mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-31922: Do not connect UDP sockets when broadcast is allowed (GH-423)
*Moved from python/asyncio#493.* This PR fixes issue python/asyncio#480, as explained in [this comment](https://github.com/python/asyncio/issues/480#issuecomment-278703828). The `_SelectorDatagramTransport.sendto` method has to be modified ~~so `_sock.sendto` is used in all cases (because it is tricky to reliably tell if the socket is connected or not). Could that be an issue for connected sockets?~~ *EDIT* ... so `_sock.send` is used only if `_sock` is connected. It also protects `socket.getsockname` against `OSError` in `_SelectorTransport`. This might happen on Windows if the socket is not connected (e.g. for UDP broadcasting). https://bugs.python.org/issue31922
This commit is contained in:
parent
91cc01f40e
commit
63deaa5b70
5 changed files with 34 additions and 7 deletions
|
@ -1306,7 +1306,8 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
if local_addr:
|
if local_addr:
|
||||||
sock.bind(local_address)
|
sock.bind(local_address)
|
||||||
if remote_addr:
|
if remote_addr:
|
||||||
await self.sock_connect(sock, remote_address)
|
if not allow_broadcast:
|
||||||
|
await self.sock_connect(sock, remote_address)
|
||||||
r_addr = remote_address
|
r_addr = remote_address
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
if sock is not None:
|
if sock is not None:
|
||||||
|
|
|
@ -587,7 +587,10 @@ class _SelectorTransport(transports._FlowControlMixin,
|
||||||
def __init__(self, loop, sock, protocol, extra=None, server=None):
|
def __init__(self, loop, sock, protocol, extra=None, server=None):
|
||||||
super().__init__(extra, loop)
|
super().__init__(extra, loop)
|
||||||
self._extra['socket'] = sock
|
self._extra['socket'] = sock
|
||||||
self._extra['sockname'] = sock.getsockname()
|
try:
|
||||||
|
self._extra['sockname'] = sock.getsockname()
|
||||||
|
except OSError:
|
||||||
|
self._extra['sockname'] = None
|
||||||
if 'peername' not in self._extra:
|
if 'peername' not in self._extra:
|
||||||
try:
|
try:
|
||||||
self._extra['peername'] = sock.getpeername()
|
self._extra['peername'] = sock.getpeername()
|
||||||
|
@ -976,9 +979,11 @@ class _SelectorDatagramTransport(_SelectorTransport):
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._address and addr not in (None, self._address):
|
if self._address:
|
||||||
raise ValueError(
|
if addr not in (None, self._address):
|
||||||
f'Invalid address: must be None or {self._address}')
|
raise ValueError(
|
||||||
|
f'Invalid address: must be None or {self._address}')
|
||||||
|
addr = self._address
|
||||||
|
|
||||||
if self._conn_lost and self._address:
|
if self._conn_lost and self._address:
|
||||||
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
|
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
|
||||||
|
@ -989,7 +994,7 @@ class _SelectorDatagramTransport(_SelectorTransport):
|
||||||
if not self._buffer:
|
if not self._buffer:
|
||||||
# Attempt to send it right away first.
|
# Attempt to send it right away first.
|
||||||
try:
|
try:
|
||||||
if self._address:
|
if self._extra['peername']:
|
||||||
self._sock.send(data)
|
self._sock.send(data)
|
||||||
else:
|
else:
|
||||||
self._sock.sendto(data, addr)
|
self._sock.sendto(data, addr)
|
||||||
|
@ -1012,7 +1017,7 @@ class _SelectorDatagramTransport(_SelectorTransport):
|
||||||
while self._buffer:
|
while self._buffer:
|
||||||
data, addr = self._buffer.popleft()
|
data, addr = self._buffer.popleft()
|
||||||
try:
|
try:
|
||||||
if self._address:
|
if self._extra['peername']:
|
||||||
self._sock.send(data)
|
self._sock.send(data)
|
||||||
else:
|
else:
|
||||||
self._sock.sendto(data, addr)
|
self._sock.sendto(data, addr)
|
||||||
|
|
|
@ -1586,6 +1586,23 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
OSError, self.loop.run_until_complete, coro)
|
OSError, self.loop.run_until_complete, coro)
|
||||||
|
|
||||||
|
def test_create_datagram_endpoint_allow_broadcast(self):
|
||||||
|
protocol = MyDatagramProto(create_future=True, loop=self.loop)
|
||||||
|
self.loop.sock_connect = sock_connect = mock.Mock()
|
||||||
|
sock_connect.return_value = []
|
||||||
|
|
||||||
|
coro = self.loop.create_datagram_endpoint(
|
||||||
|
lambda: protocol,
|
||||||
|
remote_addr=('127.0.0.1', 0),
|
||||||
|
allow_broadcast=True)
|
||||||
|
|
||||||
|
transport, _ = self.loop.run_until_complete(coro)
|
||||||
|
self.assertFalse(sock_connect.called)
|
||||||
|
|
||||||
|
transport.close()
|
||||||
|
self.loop.run_until_complete(protocol.done)
|
||||||
|
self.assertEqual('CLOSED', protocol.state)
|
||||||
|
|
||||||
@patch_socket
|
@patch_socket
|
||||||
def test_create_datagram_endpoint_socket_err(self, m_socket):
|
def test_create_datagram_endpoint_socket_err(self, m_socket):
|
||||||
m_socket.getaddrinfo = socket.getaddrinfo
|
m_socket.getaddrinfo = socket.getaddrinfo
|
||||||
|
|
|
@ -1065,6 +1065,7 @@ class SelectorDatagramTransportTests(test_utils.TestCase):
|
||||||
self.sock.fileno.return_value = 7
|
self.sock.fileno.return_value = 7
|
||||||
|
|
||||||
def datagram_transport(self, address=None):
|
def datagram_transport(self, address=None):
|
||||||
|
self.sock.getpeername.side_effect = None if address else OSError
|
||||||
transport = _SelectorDatagramTransport(self.loop, self.sock,
|
transport = _SelectorDatagramTransport(self.loop, self.sock,
|
||||||
self.protocol,
|
self.protocol,
|
||||||
address=address)
|
address=address)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
:meth:`asyncio.AbstractEventLoop.create_datagram_endpoint`:
|
||||||
|
Do not connect UDP socket when broadcast is allowed.
|
||||||
|
This allows to receive replies after a UDP broadcast.
|
Loading…
Add table
Add a link
Reference in a new issue