From d9d49b9ef65c55c362a3923230b8848011204d67 Mon Sep 17 00:00:00 2001 From: twisteroid ambassador Date: Wed, 2 Jan 2019 16:47:20 +0800 Subject: [PATCH 1/3] Clarify purpose of _ensure_resolved() and enforce usage. High-level APIs such as create_connection() accept (host, port), while low-level APIs such as sock_connect() accept socket address tuples. _ensure_resolved() should be used exactly once to turn (host, port) into an address tuple. The signature of _ensure_resolved() was changed from (address, *, ...) to (host, port, *, ...) to enforce this. --- Lib/asyncio/base_events.py | 27 +++++++++++++++------------ Lib/asyncio/selector_events.py | 9 ++++----- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 60a189bdfb7ec9..c5c76b09c1f83c 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -141,6 +141,8 @@ def _ipaddr_info(host, port, family, type, proto): if '%' in host: # Linux's inet_pton doesn't accept an IPv6 zone index after host, # like '::1%lo0'. + # Such hosts should be resolved by getaddrinfo() to obtain the + # appropriate scope ID, and to have the zone part removed. return None for af in afs: @@ -917,16 +919,16 @@ async def create_connection( 'host/port and sock can not be specified at the same time') infos = await self._ensure_resolved( - (host, port), family=family, - type=socket.SOCK_STREAM, proto=proto, flags=flags, loop=self) + host, port, family=family, + type=socket.SOCK_STREAM, proto=proto, flags=flags) if not infos: raise OSError('getaddrinfo() returned empty list') if local_addr is not None: laddr_infos = await self._ensure_resolved( - local_addr, family=family, + local_addr[0], local_addr[1], family=family, type=socket.SOCK_STREAM, proto=proto, - flags=flags, loop=self) + flags=flags) if not laddr_infos: raise OSError('getaddrinfo() returned empty list') @@ -1195,8 +1197,8 @@ async def create_datagram_endpoint(self, protocol_factory, '2-tuple is expected') infos = await self._ensure_resolved( - addr, family=family, type=socket.SOCK_DGRAM, - proto=proto, flags=flags, loop=self) + *addr, family=family, type=socket.SOCK_DGRAM, + proto=proto, flags=flags) if not infos: raise OSError('getaddrinfo() returned empty list') @@ -1277,22 +1279,23 @@ async def create_datagram_endpoint(self, protocol_factory, return transport, protocol - async def _ensure_resolved(self, address, *, + async def _ensure_resolved(self, host, port, *, family=0, type=socket.SOCK_STREAM, - proto=0, flags=0, loop): - host, port = address[:2] + proto=0, flags=0): + # Resolve (host, port) into a socket address tuple, skipping + # getaddrinfo() when possible. info = _ipaddr_info(host, port, family, type, proto) if info is not None: # "host" is already a resolved IP. return [info] else: - return await loop.getaddrinfo(host, port, family=family, type=type, + return await self.getaddrinfo(host, port, family=family, type=type, proto=proto, flags=flags) async def _create_server_getaddrinfo(self, host, port, family, flags): - infos = await self._ensure_resolved((host, port), family=family, + infos = await self._ensure_resolved(host, port, family=family, type=socket.SOCK_STREAM, - flags=flags, loop=self) + flags=flags) if not infos: raise OSError(f'getaddrinfo({host!r}) returned empty list') return infos diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 112c4b15b8d8cf..884f1d31ed066f 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -458,16 +458,15 @@ def _sock_sendall(self, fut, sock, data): async def sock_connect(self, sock, address): """Connect to a remote socket at address. + address must be a socket address tuple(i.e. (ipv4_address, port) for + IPv4 or (ipv6_address, port, flowinfo, scopeid) for IPv6). It must + not be a (host, port) tuple that still needs to be resolved. + This method is a coroutine. """ if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") - if not hasattr(socket, 'AF_UNIX') or sock.family != socket.AF_UNIX: - resolved = await self._ensure_resolved( - address, family=sock.family, proto=sock.proto, loop=self) - _, _, _, _, address = resolved[0] - fut = self.create_future() self._sock_connect(fut, sock, address) return await fut From 6a9f388e6b1403a028a6b8dc8dee3b7c9815ac2a Mon Sep 17 00:00:00 2001 From: twisteroid ambassador Date: Wed, 2 Jan 2019 19:19:44 +0800 Subject: [PATCH 2/3] Create NEWS entry. --- .../next/Library/2019-01-02-19-19-10.bpo-35545.lLbmO-.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-01-02-19-19-10.bpo-35545.lLbmO-.rst diff --git a/Misc/NEWS.d/next/Library/2019-01-02-19-19-10.bpo-35545.lLbmO-.rst b/Misc/NEWS.d/next/Library/2019-01-02-19-19-10.bpo-35545.lLbmO-.rst new file mode 100644 index 00000000000000..3094cccedb70ee --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-01-02-19-19-10.bpo-35545.lLbmO-.rst @@ -0,0 +1,2 @@ +Fix mishandling of scoped IPv6 addresses in +BaseEventLoop.create_connection(). From 58743a061f99a7400ed51a3b7ef7b0cda55bbd2a Mon Sep 17 00:00:00 2001 From: twisteroid ambassador Date: Fri, 4 Jan 2019 13:56:43 +0800 Subject: [PATCH 3/3] Fix punctuation. --- Lib/asyncio/selector_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 884f1d31ed066f..e7e7dc9a0d9f85 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -458,7 +458,7 @@ def _sock_sendall(self, fut, sock, data): async def sock_connect(self, sock, address): """Connect to a remote socket at address. - address must be a socket address tuple(i.e. (ipv4_address, port) for + `address` must be a socket address tuple(i.e. (ipv4_address, port) for IPv4 or (ipv6_address, port, flowinfo, scopeid) for IPv6). It must not be a (host, port) tuple that still needs to be resolved.