From f3408f648c56931a61e1d4f83c2daf5a76067f10 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Fri, 30 Dec 2022 07:32:52 +0000 Subject: [PATCH 1/3] skip local addresses of different family --- Lib/asyncio/base_events.py | 5 ++++- Lib/test/test_asyncio/test_events.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index f2f93758c3a817..183a595f376063 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -961,7 +961,10 @@ async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None): sock = socket.socket(family=family, type=type_, proto=proto) sock.setblocking(False) if local_addr_infos is not None: - for _, _, _, _, laddr in local_addr_infos: + for lfamily, _, _, _, laddr in local_addr_infos: + # skip local addresses of different family + if lfamily != family: + continue try: sock.bind(laddr) break diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index e7771edd2e4afb..b340a0ce21c275 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -670,6 +670,17 @@ def test_create_connection_local_addr(self): self.assertEqual(port, expected) tr.close() + def test_create_connection_local_addr_1(self): + # See https://github.com/python/cpython/issues/86508 + port1 = socket_helper.find_unused_port() + port2 = socket_helper.find_unused_port() + f = self.loop.create_connection( + lambda: MyProto(loop=self.loop), + 'localhost', port1, local_addr=('localhost', port2)) + + with self.assertRaises(OSError): + self.loop.run_until_complete(f) + def test_create_connection_local_addr_in_use(self): with test_utils.run_test_server() as httpd: f = self.loop.create_connection( From 3e24970867ac89dc71a2525e9f6ad3741f4ee57f Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 30 Dec 2022 07:49:09 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-12-30-07-49-08.gh-issue-86508.nGZDzC.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-12-30-07-49-08.gh-issue-86508.nGZDzC.rst diff --git a/Misc/NEWS.d/next/Library/2022-12-30-07-49-08.gh-issue-86508.nGZDzC.rst b/Misc/NEWS.d/next/Library/2022-12-30-07-49-08.gh-issue-86508.nGZDzC.rst new file mode 100644 index 00000000000000..aedad0f252c2c6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-12-30-07-49-08.gh-issue-86508.nGZDzC.rst @@ -0,0 +1 @@ +Fix :func:`asyncio.open_connection` to skip binding to local addresses of different family. Patch by Kumar Aditya. From ff4a9e195047f22e1fbe0e5ff5c65a0df10fa0a6 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Tue, 3 Jan 2023 13:25:46 +0530 Subject: [PATCH 3/3] core review & add more tests --- Lib/asyncio/base_events.py | 5 ++++- Lib/test/test_asyncio/test_events.py | 32 +++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 183a595f376063..cbabb43ae0600a 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -977,7 +977,10 @@ async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None): exc = OSError(exc.errno, msg) my_exceptions.append(exc) else: # all bind attempts failed - raise my_exceptions.pop() + if my_exceptions: + raise my_exceptions.pop() + else: + raise OSError(f"no matching local address with {family=} found") await self.sock_connect(sock, address) return sock except OSError as exc: diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index b340a0ce21c275..093f8c19b18058 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -670,10 +670,40 @@ def test_create_connection_local_addr(self): self.assertEqual(port, expected) tr.close() - def test_create_connection_local_addr_1(self): + def test_create_connection_local_addr_skip_different_family(self): # See https://github.com/python/cpython/issues/86508 port1 = socket_helper.find_unused_port() port2 = socket_helper.find_unused_port() + getaddrinfo_orig = self.loop.getaddrinfo + + async def getaddrinfo(host, port, *args, **kwargs): + if port == port2: + return [(socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('::1', 0, 0, 0)), + (socket.AF_INET, socket.SOCK_STREAM, 0, '', ('127.0.0.1', 0))] + return await getaddrinfo_orig(host, port, *args, **kwargs) + + self.loop.getaddrinfo = getaddrinfo + + f = self.loop.create_connection( + lambda: MyProto(loop=self.loop), + 'localhost', port1, local_addr=('localhost', port2)) + + with self.assertRaises(OSError): + self.loop.run_until_complete(f) + + def test_create_connection_local_addr_nomatch_family(self): + # See https://github.com/python/cpython/issues/86508 + port1 = socket_helper.find_unused_port() + port2 = socket_helper.find_unused_port() + getaddrinfo_orig = self.loop.getaddrinfo + + async def getaddrinfo(host, port, *args, **kwargs): + if port == port2: + return [(socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('::1', 0, 0, 0))] + return await getaddrinfo_orig(host, port, *args, **kwargs) + + self.loop.getaddrinfo = getaddrinfo + f = self.loop.create_connection( lambda: MyProto(loop=self.loop), 'localhost', port1, local_addr=('localhost', port2))