Skip to content

Requests with IPv6 Link-Local address and Zone Identifier fail #10314

Open
@ph1l1p139

Description

@ph1l1p139

Describe the bug

With aiohttp versions from 3.10 onwards, HTTP client requests to IPv6 link-local addresses with Zone Identifiers no longer work.

Previous versions (3.7 and 3.9 tested) work when using zone Identifiers.
I can't see any information about this change in behaviour in the changelogs so I think it is a bug.

IPv6 Zone identifiers in address literals/URIs are described in RFC6874. They specify a specific network interface to use when communicating with scoped IPv6 addresses wiki. On windows the interface is identified by the ifIndex from Get-NetAdapter in Powershell, in Linux it is the interface name.

In previous versions a request to a URL of the form https://[fe80::255:daff:fe40:6158%5]:8092/SerialNumber would work (although it should be noted that this is not actually RFC compliant as the % literal is not escaped as %25).

In current versions the request times out without sending any packets (checked in wireshark) and a aiohttp.client_exceptions.ClientConnectorError is raised.

To Reproduce

  1. Set up an IPv6 capable device as a server on your local network and record the IPv6 link local address (starts fe80::).
  2. Identify the zone index of the network interface attached to the local network with the above device. On windows you can use Get-NetAdapter and record the ifIndex, on Linux use ifconfig to get the interface name (e.g eth1).
  3. Implement the client as below, modifying the IPv6 address and zone identifier to match those recorded above, and modifying the rest of the URL to match an endpoint on your server.
  4. Run the client to send the request.
import asyncio
import aiohttp

async def get_serial():
    url = "https://[fe80::255:daff:fe40:6158%5]:8092/SerialNumber"
    headers = {'Content-Type': 'application/json'}

    print(f"Attempting to GET {url}")

    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers ) as resp:
            if resp.status != 200:
                raise ResponseError(f"Received unexpected response {resp.status} from {url}")
            parsed = await resp.json()

    print(f"Server has serial number {parsed['SerialNumber']}")

if __name__ == "__main__":
    asyncio.run(get_serial())

Expected behavior

A HTTP request is sent to the server and the response is received correctly.

Logs/tracebacks

> uv run -p 3.13 .\aiohttp-testcase.py
Reading inline script metadata from `aiohttp-testcase.py`
Attempting to GET https://[fe80::255:daff:fe40:6158%5]:8092/SerialNumber
Traceback (most recent call last):
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\connector.py", line 1115, in _wrap_create_connection
    sock = await aiohappyeyeballs.start_connection(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
    )
    ^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohappyeyeballs\impl.py", line 104, in start_connection
    raise first_exception
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohappyeyeballs\impl.py", line 82, in start_connection
    sock = await _connect_sock(
           ^^^^^^^^^^^^^^^^^^^^
        current_loop, exceptions, addrinfo, local_addr_infos
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohappyeyeballs\impl.py", line 174, in _connect_sock
    await loop.sock_connect(sock, address)
  File "C:\Users\PhilipD\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\proactor_events.py", line 726, in sock_connect
    return await self._proactor.connect(sock, address)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\PhilipD\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\windows_events.py", line 804, in _poll
    value = callback(transferred, key, ov)
  File "C:\Users\PhilipD\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\windows_events.py", line 600, in finish_connect
    ov.getresult()
    ~~~~~~~~~~~~^^
OSError: [WinError 121] The semaphore timeout period has expired

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\git\Aligo\DS10G\yocto\scripts\nsg-upgrade\aiohttp-testcase.py", line 34, in <module>
    asyncio.run(get_serial())
    ~~~~~~~~~~~^^^^^^^^^^^^^^
  File "C:\Users\PhilipD\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "C:\Users\PhilipD\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "C:\Users\PhilipD\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\base_events.py", line 720, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "C:\git\Aligo\DS10G\yocto\scripts\nsg-upgrade\aiohttp-testcase.py", line 26, in get_serial
    async with session.get(url, headers=headers, ssl=ssl_context ) as resp:
               ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\client.py", line 1425, in __aenter__
    self._resp: _RetType = await self._coro
                           ^^^^^^^^^^^^^^^^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\client.py", line 703, in _request
    conn = await self._connector.connect(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        req, traces=traces, timeout=real_timeout
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\connector.py", line 548, in connect
    proto = await self._create_connection(req, traces, timeout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\connector.py", line 1056, in _create_connection
    _, proto = await self._create_direct_connection(req, traces, timeout)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\connector.py", line 1400, in _create_direct_connection
    raise last_exc
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\connector.py", line 1369, in _create_direct_connection
    transp, proto = await self._wrap_create_connection(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<7 lines>...
    )
    ^
  File "C:\Users\PhilipD\AppData\Local\uv\cache\archive-v0\zZOczEdLHPBOvMpn0PUvM\Lib\site-packages\aiohttp\connector.py", line 1130, in _wrap_create_connection
    raise client_error(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host fe80::255:daff:fe40:6158%5:8092 ssl:<ssl.SSLContext object at 0x0000029E21368B00> [The semaphore timeout period has expired]

Python Version

3.13.1

aiohttp Version

3.11.11

multidict Version

6.1.0

propcache Version

0.2.1

yarl Version

1.18.3

OS

Windows 11

Related component

Client

Additional context

This behaviour is observed using IPv6 link local addresses which are on the local network. The internet and proxies etc are not involved.

My specific test case uses https as our server requires SSL with a self signed certificate but I don't think this is related to the issue as no network packets are ever being sent.

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions