Skip to content

Commit 19ca5b5

Browse files
bpo-31922: Do not connect UDP sockets when broadcast is allowed (GH-423)
*Moved from python/asyncioGH-493.* This PR fixes issue python/asyncioGH-480, as explained in [this comment](https://github.com/python/asyncio/issues/480GH-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 (cherry picked from commit 63deaa5) Co-authored-by: Vincent Michel <[email protected]>
1 parent 1fe722c commit 19ca5b5

File tree

5 files changed

+34
-7
lines changed

5 files changed

+34
-7
lines changed

Lib/asyncio/base_events.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,8 @@ async def create_datagram_endpoint(self, protocol_factory,
12291229
if local_addr:
12301230
sock.bind(local_address)
12311231
if remote_addr:
1232-
await self.sock_connect(sock, remote_address)
1232+
if not allow_broadcast:
1233+
await self.sock_connect(sock, remote_address)
12331234
r_addr = remote_address
12341235
except OSError as exc:
12351236
if sock is not None:

Lib/asyncio/selector_events.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,10 @@ class _SelectorTransport(transports._FlowControlMixin,
578578
def __init__(self, loop, sock, protocol, extra=None, server=None):
579579
super().__init__(extra, loop)
580580
self._extra['socket'] = sock
581-
self._extra['sockname'] = sock.getsockname()
581+
try:
582+
self._extra['sockname'] = sock.getsockname()
583+
except OSError:
584+
self._extra['sockname'] = None
582585
if 'peername' not in self._extra:
583586
try:
584587
self._extra['peername'] = sock.getpeername()
@@ -968,9 +971,11 @@ def sendto(self, data, addr=None):
968971
if not data:
969972
return
970973

971-
if self._address and addr not in (None, self._address):
972-
raise ValueError(
973-
f'Invalid address: must be None or {self._address}')
974+
if self._address:
975+
if addr not in (None, self._address):
976+
raise ValueError(
977+
f'Invalid address: must be None or {self._address}')
978+
addr = self._address
974979

975980
if self._conn_lost and self._address:
976981
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
@@ -981,7 +986,7 @@ def sendto(self, data, addr=None):
981986
if not self._buffer:
982987
# Attempt to send it right away first.
983988
try:
984-
if self._address:
989+
if self._extra['peername']:
985990
self._sock.send(data)
986991
else:
987992
self._sock.sendto(data, addr)
@@ -1004,7 +1009,7 @@ def _sendto_ready(self):
10041009
while self._buffer:
10051010
data, addr = self._buffer.popleft()
10061011
try:
1007-
if self._address:
1012+
if self._extra['peername']:
10081013
self._sock.send(data)
10091014
else:
10101015
self._sock.sendto(data, addr)

Lib/test/test_asyncio/test_base_events.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,23 @@ def test_create_datagram_endpoint_connect_err(self):
15691569
self.assertRaises(
15701570
OSError, self.loop.run_until_complete, coro)
15711571

1572+
def test_create_datagram_endpoint_allow_broadcast(self):
1573+
protocol = MyDatagramProto(create_future=True, loop=self.loop)
1574+
self.loop.sock_connect = sock_connect = mock.Mock()
1575+
sock_connect.return_value = []
1576+
1577+
coro = self.loop.create_datagram_endpoint(
1578+
lambda: protocol,
1579+
remote_addr=('127.0.0.1', 0),
1580+
allow_broadcast=True)
1581+
1582+
transport, _ = self.loop.run_until_complete(coro)
1583+
self.assertFalse(sock_connect.called)
1584+
1585+
transport.close()
1586+
self.loop.run_until_complete(protocol.done)
1587+
self.assertEqual('CLOSED', protocol.state)
1588+
15721589
@patch_socket
15731590
def test_create_datagram_endpoint_socket_err(self, m_socket):
15741591
m_socket.getaddrinfo = socket.getaddrinfo

Lib/test/test_asyncio/test_selector_events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,7 @@ def setUp(self):
14671467
self.sock.fileno.return_value = 7
14681468

14691469
def datagram_transport(self, address=None):
1470+
self.sock.getpeername.side_effect = None if address else OSError
14701471
transport = _SelectorDatagramTransport(self.loop, self.sock,
14711472
self.protocol,
14721473
address=address)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`asyncio.AbstractEventLoop.create_datagram_endpoint`:
2+
Do not connect UDP socket when broadcast is allowed.
3+
This allows to receive replies after a UDP broadcast.

0 commit comments

Comments
 (0)