Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ab513a3

Browse files
aerosambv
authored andcommitted
bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADDR (#17311)
1 parent 82b4950 commit ab513a3

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
@@ -473,6 +473,21 @@ Opening network connections
473473
reuse_address=None, reuse_port=None, \
474474
allow_broadcast=None, sock=None)
475475

476+
.. note::
477+
The parameter *reuse_address* is no longer supported, as using
478+
:py:data:`~sockets.SO_REUSEADDR` poses a significant security concern for
479+
UDP. Explicitly passing ``reuse_address=True`` will raise an exception.
480+
481+
When multiple processes with differing UIDs assign sockets to an
482+
indentical UDP socket address with ``SO_REUSEADDR``, incoming packets can
483+
become randomly distributed among the sockets.
484+
485+
For supported platforms, *reuse_port* can be used as a replacement for
486+
similar functionality. With *reuse_port*,
487+
:py:data:`~sockets.SO_REUSEPORT` is used instead, which specifically
488+
prevents processes with differing UIDs from assigning sockets to the same
489+
socket address.
490+
476491
Create a datagram connection.
477492

478493
The socket family can be either :py:data:`~socket.AF_INET`,
@@ -501,11 +516,6 @@ Opening network connections
501516
resolution. If given, these should all be integers from the
502517
corresponding :mod:`socket` module constants.
503518

504-
* *reuse_address* tells the kernel to reuse a local socket in
505-
``TIME_WAIT`` state, without waiting for its natural timeout to
506-
expire. If not specified will automatically be set to ``True`` on
507-
Unix.
508-
509519
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
510520
same port as other existing endpoints are bound to, so long as they all
511521
set this flag when being created. This option is not supported on Windows
@@ -527,6 +537,10 @@ Opening network connections
527537
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
528538
*allow_broadcast*, and *sock* parameters were added.
529539

540+
.. versionchanged:: 3.8.1
541+
The *reuse_address* parameter is no longer supported due to security
542+
concerns.
543+
530544
.. versionchanged:: 3.8
531545
Added support for Windows.
532546

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
@@ -1230,7 +1234,7 @@ async def start_tls(self, transport, protocol, sslcontext, *,
12301234
async def create_datagram_endpoint(self, protocol_factory,
12311235
local_addr=None, remote_addr=None, *,
12321236
family=0, proto=0, flags=0,
1233-
reuse_address=None, reuse_port=None,
1237+
reuse_address=_unset, reuse_port=None,
12341238
allow_broadcast=None, sock=None):
12351239
"""Create datagram connection."""
12361240
if sock is not None:
@@ -1239,7 +1243,7 @@ async def create_datagram_endpoint(self, protocol_factory,
12391243
f'A UDP Socket was expected, got {sock!r}')
12401244
if (local_addr or remote_addr or
12411245
family or proto or flags or
1242-
reuse_address or reuse_port or allow_broadcast):
1246+
reuse_port or allow_broadcast):
12431247
# show the problematic kwargs in exception msg
12441248
opts = dict(local_addr=local_addr, remote_addr=remote_addr,
12451249
family=family, proto=proto, flags=flags,
@@ -1306,8 +1310,18 @@ async def create_datagram_endpoint(self, protocol_factory,
13061310

13071311
exceptions = []
13081312

1309-
if reuse_address is None:
1310-
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
1313+
# bpo-37228
1314+
if reuse_address is not _unset:
1315+
if reuse_address:
1316+
raise ValueError("Passing `reuse_address=True` is no "
1317+
"longer supported, as the usage of "
1318+
"SO_REUSEPORT in UDP poses a significant "
1319+
"security concern.")
1320+
else:
1321+
warnings.warn("The *reuse_address* parameter has been "
1322+
"deprecated as of 3.5.10 and is scheduled "
1323+
"for removal in 3.11.", DeprecationWarning,
1324+
stacklevel=2)
13111325

13121326
for ((family, proto),
13131327
(local_address, remote_address)) in addr_pairs_info:
@@ -1316,9 +1330,6 @@ async def create_datagram_endpoint(self, protocol_factory,
13161330
try:
13171331
sock = socket.socket(
13181332
family=family, type=socket.SOCK_DGRAM, proto=proto)
1319-
if reuse_address:
1320-
sock.setsockopt(
1321-
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
13221333
if reuse_port:
13231334
_set_reuseport(sock)
13241335
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
@@ -1738,10 +1738,6 @@ class FakeSock:
17381738
MyDatagramProto, flags=1, sock=FakeSock())
17391739
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
17401740

1741-
fut = self.loop.create_datagram_endpoint(
1742-
MyDatagramProto, reuse_address=True, sock=FakeSock())
1743-
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
1744-
17451741
fut = self.loop.create_datagram_endpoint(
17461742
MyDatagramProto, reuse_port=True, sock=FakeSock())
17471743
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
@@ -1752,7 +1748,6 @@ class FakeSock:
17521748

17531749
def test_create_datagram_endpoint_sockopts(self):
17541750
# Socket options should not be applied unless asked for.
1755-
# SO_REUSEADDR defaults to on for UNIX.
17561751
# SO_REUSEPORT is not available on all platforms.
17571752

17581753
coro = self.loop.create_datagram_endpoint(
@@ -1761,18 +1756,8 @@ def test_create_datagram_endpoint_sockopts(self):
17611756
transport, protocol = self.loop.run_until_complete(coro)
17621757
sock = transport.get_extra_info('socket')
17631758

1764-
reuse_address_default_on = (
1765-
os.name == 'posix' and sys.platform != 'cygwin')
17661759
reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
17671760

1768-
if reuse_address_default_on:
1769-
self.assertTrue(
1770-
sock.getsockopt(
1771-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
1772-
else:
1773-
self.assertFalse(
1774-
sock.getsockopt(
1775-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
17761761
if reuseport_supported:
17771762
self.assertFalse(
17781763
sock.getsockopt(
@@ -1788,13 +1773,12 @@ def test_create_datagram_endpoint_sockopts(self):
17881773
coro = self.loop.create_datagram_endpoint(
17891774
lambda: MyDatagramProto(create_future=True, loop=self.loop),
17901775
local_addr=('127.0.0.1', 0),
1791-
reuse_address=True,
17921776
reuse_port=reuseport_supported,
17931777
allow_broadcast=True)
17941778
transport, protocol = self.loop.run_until_complete(coro)
17951779
sock = transport.get_extra_info('socket')
17961780

1797-
self.assertTrue(
1781+
self.assertFalse(
17981782
sock.getsockopt(
17991783
socket.SOL_SOCKET, socket.SO_REUSEADDR))
18001784
if reuseport_supported:
@@ -1809,6 +1793,29 @@ def test_create_datagram_endpoint_sockopts(self):
18091793
self.loop.run_until_complete(protocol.done)
18101794
self.assertEqual('CLOSED', protocol.state)
18111795

1796+
def test_create_datagram_endpoint_reuse_address_error(self):
1797+
# bpo-37228: Ensure that explicit passing of `reuse_address=True`
1798+
# raises an error, as it is not safe to use SO_REUSEADDR when using UDP
1799+
1800+
coro = self.loop.create_datagram_endpoint(
1801+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1802+
local_addr=('127.0.0.1', 0),
1803+
reuse_address=True)
1804+
1805+
with self.assertRaises(ValueError):
1806+
self.loop.run_until_complete(coro)
1807+
1808+
def test_create_datagram_endpoint_reuse_address_warning(self):
1809+
# bpo-37228: Deprecate *reuse_address* parameter
1810+
1811+
coro = self.loop.create_datagram_endpoint(
1812+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1813+
local_addr=('127.0.0.1', 0),
1814+
reuse_address=False)
1815+
1816+
with self.assertWarns(DeprecationWarning):
1817+
self.loop.run_until_complete(coro)
1818+
18121819
@patch_socket
18131820
def test_create_datagram_endpoint_nosoreuseport(self, m_socket):
18141821
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)