Skip to content

Commit 3b4b16b

Browse files
committed
asyncio: Do not connect UDP sockets when broadcast is allowed
This fixes asyncio issue #480. The _SelectorDatagramTransport.sendto method has to be modified 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).
1 parent ef24b6c commit 3b4b16b

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
@@ -1213,7 +1213,8 @@ async def create_datagram_endpoint(self, protocol_factory,
12131213
if local_addr:
12141214
sock.bind(local_address)
12151215
if remote_addr:
1216-
await self.sock_connect(sock, remote_address)
1216+
if not allow_broadcast:
1217+
await self.sock_connect(sock, remote_address)
12171218
r_addr = remote_address
12181219
except OSError as exc:
12191220
if sock is not None:

Lib/asyncio/selector_events.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,10 @@ class _SelectorTransport(transports._FlowControlMixin,
589589
def __init__(self, loop, sock, protocol, extra=None, server=None):
590590
super().__init__(extra, loop)
591591
self._extra['socket'] = sock
592-
self._extra['sockname'] = sock.getsockname()
592+
try:
593+
self._extra['sockname'] = sock.getsockname()
594+
except OSError:
595+
self._extra['sockname'] = None
593596
if 'peername' not in self._extra:
594597
try:
595598
self._extra['peername'] = sock.getpeername()
@@ -979,9 +982,11 @@ def sendto(self, data, addr=None):
979982
if not data:
980983
return
981984

982-
if self._address and addr not in (None, self._address):
983-
raise ValueError(
984-
f'Invalid address: must be None or {self._address}')
985+
if self._address:
986+
if addr not in (None, self._address):
987+
raise ValueError(
988+
f'Invalid address: must be None or {self._address}')
989+
addr = self._address
985990

986991
if self._conn_lost and self._address:
987992
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
@@ -992,7 +997,7 @@ def sendto(self, data, addr=None):
992997
if not self._buffer:
993998
# Attempt to send it right away first.
994999
try:
995-
if self._address:
1000+
if self._extra['peername']:
9961001
self._sock.send(data)
9971002
else:
9981003
self._sock.sendto(data, addr)
@@ -1015,7 +1020,7 @@ def _sendto_ready(self):
10151020
while self._buffer:
10161021
data, addr = self._buffer.popleft()
10171022
try:
1018-
if self._address:
1023+
if self._extra['peername']:
10191024
self._sock.send(data)
10201025
else:
10211026
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
@@ -1485,6 +1485,23 @@ def test_create_datagram_endpoint_connect_err(self):
14851485
self.assertRaises(
14861486
OSError, self.loop.run_until_complete, coro)
14871487

1488+
def test_create_datagram_endpoint_allow_broadcast(self):
1489+
protocol = MyDatagramProto(create_future=True, loop=self.loop)
1490+
self.loop.sock_connect = sock_connect = mock.Mock()
1491+
sock_connect.return_value = []
1492+
1493+
coro = self.loop.create_datagram_endpoint(
1494+
lambda: protocol,
1495+
remote_addr=('127.0.0.1', 0),
1496+
allow_broadcast=True)
1497+
1498+
transport, _ = self.loop.run_until_complete(coro)
1499+
self.assertFalse(sock_connect.called)
1500+
1501+
transport.close()
1502+
self.loop.run_until_complete(protocol.done)
1503+
self.assertEqual('CLOSED', protocol.state)
1504+
14881505
@patch_socket
14891506
def test_create_datagram_endpoint_socket_err(self, m_socket):
14901507
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
@@ -1472,6 +1472,7 @@ def setUp(self):
14721472
self.sock.fileno.return_value = 7
14731473

14741474
def datagram_transport(self, address=None):
1475+
self.sock.getpeername.side_effect = None if address else OSError
14751476
transport = _SelectorDatagramTransport(self.loop, self.sock,
14761477
self.protocol,
14771478
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)