Commit 63deaa5b authored by Vincent Michel's avatar Vincent Michel Committed by Miss Islington (bot)

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
parent 91cc01f4
...@@ -1306,7 +1306,8 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -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, ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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)
......
:meth:`asyncio.AbstractEventLoop.create_datagram_endpoint`:
Do not connect UDP socket when broadcast is allowed.
This allows to receive replies after a UDP broadcast.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment