[issue35545] asyncio.base_events.create_connection doesn't handle scoped IPv6 addresses

Максим Аристов report at bugs.python.org
Thu Dec 20 05:56:50 EST 2018


New submission from Максим Аристов <aristovmax at gmail.com>:

loop.create_connection doesn't handle ipv6 RFC4007 addresses right since 3.7 


TEST CASE
# Set up listener on link-local address fe80::1%lo
sudo ip a add dev lo fe80::1

# 3.6 handles everything fine
socat file:/dev/null tcp6-listen:12345,REUSEADDR &
python3.6 -c 'import asyncio;loop=asyncio.get_event_loop();loop.run_until_complete(loop.create_connection(lambda:asyncio.Protocol(),host="fe80::1%lo",port="12345"))'

# 3.7 and later fails
socat file:/dev/null tcp6-listen:12345,REUSEADDR &
python3.7 -c 'import asyncio;loop=asyncio.get_event_loop();loop.run_until_complete(loop.create_connection(lambda:asyncio.Protocol(),host="fe80::1%lo",port="12345"))'

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.7/asyncio/base_events.py", line 576, in run_until_complete
    return future.result()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 951, in create_connection
    raise exceptions[0]
  File "/usr/lib/python3.7/asyncio/base_events.py", line 938, in create_connection
    await self.sock_connect(sock, address)
  File "/usr/lib/python3.7/asyncio/selector_events.py", line 475, in sock_connect
    return await fut
  File "/usr/lib/python3.7/asyncio/selector_events.py", line 480, in _sock_connect
    sock.connect(address)
OSError: [Errno 22] Invalid argument


CAUSE

Upon asyncio.base_events.create_connection _ensure_resolved is called twice, first time here:
https://github.com/python/cpython/blob/3.7/Lib/asyncio/base_events.py#L908
then here through sock_connect:
https://github.com/python/cpython/blob/3.7/Lib/asyncio/base_events.py#L946
https://github.com/python/cpython/blob/3.7/Lib/asyncio/selector_events.py#L458

_ensure_resolved calls getaddrinfo, but in 3.7 implementation changed:

% python3.6 -c 'import socket;print(socket.getaddrinfo("fe80::1%lo",12345)[0][4])'
('fe80::1%lo', 12345, 0, 1)

% python3.7 -c 'import socket;print(socket.getaddrinfo("fe80::1%lo",12345)[0][4])'
('fe80::1', 12345, 0, 1)

_ensure_connect only considers host and port parts of the address tuple:
https://github.com/python/cpython/blob/3.7/Lib/asyncio/base_events.py#L1272

In case of 3.7 first call to _ensure_resolved returns
('fe80::1', 12345, 0, 1)
then second call returns
('fe80::1', 12345, 0, 0)
Notice that scope is now completely lost and is set to 0, thus actual call to socket.connect is wrong

In case of 3.6 both first and second call to _ensure_resolved return
('fe80::1%lo', 12345, 0, 1)
because in 3.6 case scope info is preserved in address and second call can derive correct address tuple

----------
components: asyncio
messages: 332211
nosy: asvetlov, yselivanov, Максим Аристов
priority: normal
severity: normal
status: open
title: asyncio.base_events.create_connection doesn't handle scoped IPv6 addresses
type: behavior
versions: Python 3.7, Python 3.8

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue35545>
_______________________________________


More information about the Python-bugs-list mailing list