From 88be5b5b787e91d9b49da34060da618180f744d2 Mon Sep 17 00:00:00 2001 From: twisteroid ambassador Date: Wed, 19 Dec 2018 21:31:44 +0800 Subject: [PATCH 1/6] Try each (remote addrinfo, local addrinfo) pair when connecting. --- Lib/asyncio/base_events.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 60a189bdfb7ec9..a0ccb3cae1931f 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -929,29 +929,27 @@ async def create_connection( flags=flags, loop=self) if not laddr_infos: raise OSError('getaddrinfo() returned empty list') + else: + laddr_infos = [(None, None, None, None, None)] exceptions = [] - for family, type, proto, cname, address in infos: + for addrinfo, laddrinfo in itertools.product(infos, laddr_infos): + family, type, proto, cname, address = addrinfo + laddr = laddrinfo[4] try: sock = socket.socket(family=family, type=type, proto=proto) sock.setblocking(False) - if local_addr is not None: - for _, _, _, _, laddr in laddr_infos: - try: - sock.bind(laddr) - break - except OSError as exc: - msg = ( - f'error while attempting to bind on ' - f'address {laddr!r}: ' - f'{exc.strerror.lower()}' - ) - exc = OSError(exc.errno, msg) - exceptions.append(exc) - else: - sock.close() - sock = None - continue + if laddr is not None: + try: + sock.bind(laddr) + break + except OSError as exc: + msg = ( + f'error while attempting to bind on ' + f'address {laddr!r}: ' + f'{exc.strerror.lower()}' + ) + raise OSError(exc.errno, msg) if self._debug: logger.debug("connect %r to %r", sock, address) await self.sock_connect(sock, address) From 04c9c6255835122dbba0bd999f0e332394d36ca7 Mon Sep 17 00:00:00 2001 From: twisteroid ambassador Date: Wed, 19 Dec 2018 23:07:15 +0800 Subject: [PATCH 2/6] Remove extraneous break. --- Lib/asyncio/base_events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index a0ccb3cae1931f..c6054623753d67 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -942,7 +942,6 @@ async def create_connection( if laddr is not None: try: sock.bind(laddr) - break except OSError as exc: msg = ( f'error while attempting to bind on ' From 7614548cc3e6f0e90b21b2eea3fb71d01886f4b4 Mon Sep 17 00:00:00 2001 From: twisteroidambassador Date: Sun, 31 Mar 2019 14:46:51 +0800 Subject: [PATCH 3/6] Add test case. --- Lib/test/test_asyncio/test_base_events.py | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 53854758a27d4c..87e297aff676e6 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1367,6 +1367,34 @@ def getaddrinfo_task(*args, **kwds): self.assertRaises( OSError, self.loop.run_until_complete, coro) + @patch_socket + def test_create_connection_socket_bind_different_family(self, m_socket): + # https://bugs.python.org/issue35302 + # Test the case where local_addr resolves to multiple addrinfos, and + # if the socket is bound to the first address, connect() will fail. + sock = m_socket.socket.return_value + + def mock_connect_check_bind(address): + if sock.bind.call_count: + bind_addr = sock.bind.call_args[0][0] + if bind_addr[0][:4] != address[0][:4]: + raise OSError + + sock.connect.side_effect = mock_connect_check_bind + + async def getaddrinfo(host, port, *args, **kwargs): + if host == 'bindaddr': + return [(2, 1, 6, '', ('192.0.2.1', port)), + (10, 1, 6, '', ('2001:db8::1', port))] + else: + return [(10, 1, 6, '', ('2001:db8::1:1', port))] + + self.loop.getaddrinfo = getaddrinfo + + coro = self.loop.create_connection( + MyProto, 'connectaddr', 80, local_addr=('bindaddr', 0)) + self.loop.run_until_complete(coro) + @patch_socket def test_create_connection_bluetooth(self, m_socket): # See http://bugs.python.org/issue27136, fallback to getaddrinfo when From 6b4edd987471f061e216224cdcdbf12c4ea773fc Mon Sep 17 00:00:00 2001 From: twisteroidambassador Date: Sun, 31 Mar 2019 14:51:34 +0800 Subject: [PATCH 4/6] Add NEWS entry. --- .../next/Library/2019-03-31-14-50-10.bpo-35302.IDJZzC.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-03-31-14-50-10.bpo-35302.IDJZzC.rst diff --git a/Misc/NEWS.d/next/Library/2019-03-31-14-50-10.bpo-35302.IDJZzC.rst b/Misc/NEWS.d/next/Library/2019-03-31-14-50-10.bpo-35302.IDJZzC.rst new file mode 100644 index 00000000000000..bb6ddbcffc7102 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-03-31-14-50-10.bpo-35302.IDJZzC.rst @@ -0,0 +1,2 @@ +Try each (remote addrinfo, local addrinfo) pair in +BaseEventLoop.create_connection(). From 6a3745149190a9a26fe49b5313349920cdf0187b Mon Sep 17 00:00:00 2001 From: twisteroidambassador Date: Fri, 5 Apr 2019 00:38:32 +0800 Subject: [PATCH 5/6] Emulate other tests where sockets are successfully connected. --- Lib/test/test_asyncio/test_base_events.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 87e297aff676e6..c68581cfee5420 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1374,6 +1374,11 @@ def test_create_connection_socket_bind_different_family(self, m_socket): # if the socket is bound to the first address, connect() will fail. sock = m_socket.socket.return_value + self.loop._add_reader = mock.Mock() + self.loop._add_reader._is_coroutine = False + self.loop._add_writer = mock.Mock() + self.loop._add_writer._is_coroutine = False + def mock_connect_check_bind(address): if sock.bind.call_count: bind_addr = sock.bind.call_args[0][0] @@ -1393,7 +1398,12 @@ async def getaddrinfo(host, port, *args, **kwargs): coro = self.loop.create_connection( MyProto, 'connectaddr', 80, local_addr=('bindaddr', 0)) - self.loop.run_until_complete(coro) + t, p = self.loop.run_until_complete(coro) + try: + sock.connect.assert_called_with(('2001:db8::1', 80)) + finally: + t.close() + test_utils.run_briefly(self.loop) @patch_socket def test_create_connection_bluetooth(self, m_socket): From 7a6a571cfdc3eec192eb5dc18127b97e5223fc90 Mon Sep 17 00:00:00 2001 From: twisteroidambassador Date: Fri, 5 Apr 2019 00:53:45 +0800 Subject: [PATCH 6/6] Use correct address in assertion. --- Lib/test/test_asyncio/test_base_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index c68581cfee5420..b3f359da402d05 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1400,7 +1400,7 @@ async def getaddrinfo(host, port, *args, **kwargs): MyProto, 'connectaddr', 80, local_addr=('bindaddr', 0)) t, p = self.loop.run_until_complete(coro) try: - sock.connect.assert_called_with(('2001:db8::1', 80)) + sock.connect.assert_called_with(('2001:db8::1:1', 80)) finally: t.close() test_utils.run_briefly(self.loop)