mirror of https://github.com/python/cpython.git
bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADDR (#17311)
This commit is contained in:
parent
82b4950b5e
commit
ab513a38c9
|
@ -473,6 +473,21 @@ Opening network connections
|
||||||
reuse_address=None, reuse_port=None, \
|
reuse_address=None, reuse_port=None, \
|
||||||
allow_broadcast=None, sock=None)
|
allow_broadcast=None, sock=None)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The parameter *reuse_address* is no longer supported, as using
|
||||||
|
:py:data:`~sockets.SO_REUSEADDR` poses a significant security concern for
|
||||||
|
UDP. Explicitly passing ``reuse_address=True`` will raise an exception.
|
||||||
|
|
||||||
|
When multiple processes with differing UIDs assign sockets to an
|
||||||
|
indentical UDP socket address with ``SO_REUSEADDR``, incoming packets can
|
||||||
|
become randomly distributed among the sockets.
|
||||||
|
|
||||||
|
For supported platforms, *reuse_port* can be used as a replacement for
|
||||||
|
similar functionality. With *reuse_port*,
|
||||||
|
:py:data:`~sockets.SO_REUSEPORT` is used instead, which specifically
|
||||||
|
prevents processes with differing UIDs from assigning sockets to the same
|
||||||
|
socket address.
|
||||||
|
|
||||||
Create a datagram connection.
|
Create a datagram connection.
|
||||||
|
|
||||||
The socket family can be either :py:data:`~socket.AF_INET`,
|
The socket family can be either :py:data:`~socket.AF_INET`,
|
||||||
|
@ -501,11 +516,6 @@ Opening network connections
|
||||||
resolution. If given, these should all be integers from the
|
resolution. If given, these should all be integers from the
|
||||||
corresponding :mod:`socket` module constants.
|
corresponding :mod:`socket` module constants.
|
||||||
|
|
||||||
* *reuse_address* tells the kernel to reuse a local socket in
|
|
||||||
``TIME_WAIT`` state, without waiting for its natural timeout to
|
|
||||||
expire. If not specified will automatically be set to ``True`` on
|
|
||||||
Unix.
|
|
||||||
|
|
||||||
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
|
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
|
||||||
same port as other existing endpoints are bound to, so long as they all
|
same port as other existing endpoints are bound to, so long as they all
|
||||||
set this flag when being created. This option is not supported on Windows
|
set this flag when being created. This option is not supported on Windows
|
||||||
|
@ -527,6 +537,10 @@ Opening network connections
|
||||||
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
|
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
|
||||||
*allow_broadcast*, and *sock* parameters were added.
|
*allow_broadcast*, and *sock* parameters were added.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8.1
|
||||||
|
The *reuse_address* parameter is no longer supported due to security
|
||||||
|
concerns.
|
||||||
|
|
||||||
.. versionchanged:: 3.8
|
.. versionchanged:: 3.8
|
||||||
Added support for Windows.
|
Added support for Windows.
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,10 @@
|
||||||
# Maximum timeout passed to select to avoid OS limitations
|
# Maximum timeout passed to select to avoid OS limitations
|
||||||
MAXIMUM_SELECT_TIMEOUT = 24 * 3600
|
MAXIMUM_SELECT_TIMEOUT = 24 * 3600
|
||||||
|
|
||||||
|
# Used for deprecation and removal of `loop.create_datagram_endpoint()`'s
|
||||||
|
# *reuse_address* parameter
|
||||||
|
_unset = object()
|
||||||
|
|
||||||
|
|
||||||
def _format_handle(handle):
|
def _format_handle(handle):
|
||||||
cb = handle._callback
|
cb = handle._callback
|
||||||
|
@ -1230,7 +1234,7 @@ async def start_tls(self, transport, protocol, sslcontext, *,
|
||||||
async def create_datagram_endpoint(self, protocol_factory,
|
async def create_datagram_endpoint(self, protocol_factory,
|
||||||
local_addr=None, remote_addr=None, *,
|
local_addr=None, remote_addr=None, *,
|
||||||
family=0, proto=0, flags=0,
|
family=0, proto=0, flags=0,
|
||||||
reuse_address=None, reuse_port=None,
|
reuse_address=_unset, reuse_port=None,
|
||||||
allow_broadcast=None, sock=None):
|
allow_broadcast=None, sock=None):
|
||||||
"""Create datagram connection."""
|
"""Create datagram connection."""
|
||||||
if sock is not None:
|
if sock is not None:
|
||||||
|
@ -1239,7 +1243,7 @@ async def create_datagram_endpoint(self, protocol_factory,
|
||||||
f'A UDP Socket was expected, got {sock!r}')
|
f'A UDP Socket was expected, got {sock!r}')
|
||||||
if (local_addr or remote_addr or
|
if (local_addr or remote_addr or
|
||||||
family or proto or flags or
|
family or proto or flags or
|
||||||
reuse_address or reuse_port or allow_broadcast):
|
reuse_port or allow_broadcast):
|
||||||
# show the problematic kwargs in exception msg
|
# show the problematic kwargs in exception msg
|
||||||
opts = dict(local_addr=local_addr, remote_addr=remote_addr,
|
opts = dict(local_addr=local_addr, remote_addr=remote_addr,
|
||||||
family=family, proto=proto, flags=flags,
|
family=family, proto=proto, flags=flags,
|
||||||
|
@ -1306,8 +1310,18 @@ async def create_datagram_endpoint(self, protocol_factory,
|
||||||
|
|
||||||
exceptions = []
|
exceptions = []
|
||||||
|
|
||||||
if reuse_address is None:
|
# bpo-37228
|
||||||
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
|
if reuse_address is not _unset:
|
||||||
|
if reuse_address:
|
||||||
|
raise ValueError("Passing `reuse_address=True` is no "
|
||||||
|
"longer supported, as the usage of "
|
||||||
|
"SO_REUSEPORT in UDP poses a significant "
|
||||||
|
"security concern.")
|
||||||
|
else:
|
||||||
|
warnings.warn("The *reuse_address* parameter has been "
|
||||||
|
"deprecated as of 3.5.10 and is scheduled "
|
||||||
|
"for removal in 3.11.", DeprecationWarning,
|
||||||
|
stacklevel=2)
|
||||||
|
|
||||||
for ((family, proto),
|
for ((family, proto),
|
||||||
(local_address, remote_address)) in addr_pairs_info:
|
(local_address, remote_address)) in addr_pairs_info:
|
||||||
|
@ -1316,9 +1330,6 @@ async def create_datagram_endpoint(self, protocol_factory,
|
||||||
try:
|
try:
|
||||||
sock = socket.socket(
|
sock = socket.socket(
|
||||||
family=family, type=socket.SOCK_DGRAM, proto=proto)
|
family=family, type=socket.SOCK_DGRAM, proto=proto)
|
||||||
if reuse_address:
|
|
||||||
sock.setsockopt(
|
|
||||||
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
if reuse_port:
|
if reuse_port:
|
||||||
_set_reuseport(sock)
|
_set_reuseport(sock)
|
||||||
if allow_broadcast:
|
if allow_broadcast:
|
||||||
|
|
|
@ -1738,10 +1738,6 @@ class FakeSock:
|
||||||
MyDatagramProto, flags=1, sock=FakeSock())
|
MyDatagramProto, flags=1, sock=FakeSock())
|
||||||
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
|
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
|
||||||
|
|
||||||
fut = self.loop.create_datagram_endpoint(
|
|
||||||
MyDatagramProto, reuse_address=True, sock=FakeSock())
|
|
||||||
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
|
|
||||||
|
|
||||||
fut = self.loop.create_datagram_endpoint(
|
fut = self.loop.create_datagram_endpoint(
|
||||||
MyDatagramProto, reuse_port=True, sock=FakeSock())
|
MyDatagramProto, reuse_port=True, sock=FakeSock())
|
||||||
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
|
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
|
||||||
|
@ -1752,7 +1748,6 @@ class FakeSock:
|
||||||
|
|
||||||
def test_create_datagram_endpoint_sockopts(self):
|
def test_create_datagram_endpoint_sockopts(self):
|
||||||
# Socket options should not be applied unless asked for.
|
# Socket options should not be applied unless asked for.
|
||||||
# SO_REUSEADDR defaults to on for UNIX.
|
|
||||||
# SO_REUSEPORT is not available on all platforms.
|
# SO_REUSEPORT is not available on all platforms.
|
||||||
|
|
||||||
coro = self.loop.create_datagram_endpoint(
|
coro = self.loop.create_datagram_endpoint(
|
||||||
|
@ -1761,18 +1756,8 @@ def test_create_datagram_endpoint_sockopts(self):
|
||||||
transport, protocol = self.loop.run_until_complete(coro)
|
transport, protocol = self.loop.run_until_complete(coro)
|
||||||
sock = transport.get_extra_info('socket')
|
sock = transport.get_extra_info('socket')
|
||||||
|
|
||||||
reuse_address_default_on = (
|
|
||||||
os.name == 'posix' and sys.platform != 'cygwin')
|
|
||||||
reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
|
reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
|
||||||
|
|
||||||
if reuse_address_default_on:
|
|
||||||
self.assertTrue(
|
|
||||||
sock.getsockopt(
|
|
||||||
socket.SOL_SOCKET, socket.SO_REUSEADDR))
|
|
||||||
else:
|
|
||||||
self.assertFalse(
|
|
||||||
sock.getsockopt(
|
|
||||||
socket.SOL_SOCKET, socket.SO_REUSEADDR))
|
|
||||||
if reuseport_supported:
|
if reuseport_supported:
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
sock.getsockopt(
|
sock.getsockopt(
|
||||||
|
@ -1788,13 +1773,12 @@ def test_create_datagram_endpoint_sockopts(self):
|
||||||
coro = self.loop.create_datagram_endpoint(
|
coro = self.loop.create_datagram_endpoint(
|
||||||
lambda: MyDatagramProto(create_future=True, loop=self.loop),
|
lambda: MyDatagramProto(create_future=True, loop=self.loop),
|
||||||
local_addr=('127.0.0.1', 0),
|
local_addr=('127.0.0.1', 0),
|
||||||
reuse_address=True,
|
|
||||||
reuse_port=reuseport_supported,
|
reuse_port=reuseport_supported,
|
||||||
allow_broadcast=True)
|
allow_broadcast=True)
|
||||||
transport, protocol = self.loop.run_until_complete(coro)
|
transport, protocol = self.loop.run_until_complete(coro)
|
||||||
sock = transport.get_extra_info('socket')
|
sock = transport.get_extra_info('socket')
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertFalse(
|
||||||
sock.getsockopt(
|
sock.getsockopt(
|
||||||
socket.SOL_SOCKET, socket.SO_REUSEADDR))
|
socket.SOL_SOCKET, socket.SO_REUSEADDR))
|
||||||
if reuseport_supported:
|
if reuseport_supported:
|
||||||
|
@ -1809,6 +1793,29 @@ def test_create_datagram_endpoint_sockopts(self):
|
||||||
self.loop.run_until_complete(protocol.done)
|
self.loop.run_until_complete(protocol.done)
|
||||||
self.assertEqual('CLOSED', protocol.state)
|
self.assertEqual('CLOSED', protocol.state)
|
||||||
|
|
||||||
|
def test_create_datagram_endpoint_reuse_address_error(self):
|
||||||
|
# bpo-37228: Ensure that explicit passing of `reuse_address=True`
|
||||||
|
# raises an error, as it is not safe to use SO_REUSEADDR when using UDP
|
||||||
|
|
||||||
|
coro = self.loop.create_datagram_endpoint(
|
||||||
|
lambda: MyDatagramProto(create_future=True, loop=self.loop),
|
||||||
|
local_addr=('127.0.0.1', 0),
|
||||||
|
reuse_address=True)
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.loop.run_until_complete(coro)
|
||||||
|
|
||||||
|
def test_create_datagram_endpoint_reuse_address_warning(self):
|
||||||
|
# bpo-37228: Deprecate *reuse_address* parameter
|
||||||
|
|
||||||
|
coro = self.loop.create_datagram_endpoint(
|
||||||
|
lambda: MyDatagramProto(create_future=True, loop=self.loop),
|
||||||
|
local_addr=('127.0.0.1', 0),
|
||||||
|
reuse_address=False)
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
self.loop.run_until_complete(coro)
|
||||||
|
|
||||||
@patch_socket
|
@patch_socket
|
||||||
def test_create_datagram_endpoint_nosoreuseport(self, m_socket):
|
def test_create_datagram_endpoint_nosoreuseport(self, m_socket):
|
||||||
del m_socket.SO_REUSEPORT
|
del m_socket.SO_REUSEPORT
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
Due to significant security concerns, the *reuse_address* parameter of
|
||||||
|
:meth:`asyncio.loop.create_datagram_endpoint` is no longer supported. This is
|
||||||
|
because of the behavior of ``SO_REUSEADDR`` in UDP. For more details, see the
|
||||||
|
documentation for ``loop.create_datagram_endpoint()``.
|
||||||
|
(Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in
|
||||||
|
:issue:`37228`.)
|
Loading…
Reference in New Issue