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..e7e7dc9a0d9f85 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 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().