Skip to content

loop.create_server does not detect if the interface is IPv6 enabled #75128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
cecton mannequin opened this issue Jul 17, 2017 · 13 comments
Closed

loop.create_server does not detect if the interface is IPv6 enabled #75128

cecton mannequin opened this issue Jul 17, 2017 · 13 comments
Labels
topic-asyncio type-bug An unexpected behavior, bug, or error

Comments

@cecton
Copy link
Mannequin

cecton mannequin commented Jul 17, 2017

BPO 30945
Nosy @pitrou, @1st1, @asottile, @jnwatson, @cecton
PRs
  • gh-75128: Fix create_server to handle the case when iface isn't IPv6 enabled. #7207
  • Files
  • test_ipv6.py: Example file to reproduce the issue
  • eaddrnotavail_asyncio.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2017-07-17.10:04:34.577>
    labels = ['type-bug', '3.7', 'expert-asyncio']
    title = 'loop.create_server does not detect if the interface is IPv6 enabled'
    updated_at = <Date 2018-10-19.21:14:34.454>
    user = 'https://github.com/cecton'

    bugs.python.org fields:

    activity = <Date 2018-10-19.21:14:34.454>
    actor = 'jnwatson'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['asyncio']
    creation = <Date 2017-07-17.10:04:34.577>
    creator = 'cecton'
    dependencies = []
    files = ['47019', '47026']
    hgrepos = []
    issue_num = 30945
    keywords = ['patch']
    message_count = 12.0
    messages = ['298474', '298683', '298684', '298714', '298716', '298718', '311915', '311916', '311917', '311942', '317895', '318045']
    nosy_count = 5.0
    nosy_names = ['pitrou', 'yselivanov', 'Anthony Sottile', 'jnwatson', 'cecton']
    pr_nums = ['7207']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue30945'
    versions = ['Python 3.5', 'Python 3.6', 'Python 3.7']

    Linked PRs

    @cecton
    Copy link
    Mannequin Author

    cecton mannequin commented Jul 17, 2017

    The IPv6 detection in asyncio.base_events.create_server only detect if IPv6 is available instead of checking if the interface can actually support it.

    I noticed that by using Python in a Docker container (example code to reproduce in attachment):

    docker run -it --rm -v /tmp/test_ipv6.py:/src/test_ipv6.py python:3.6 python /src/test_ipv6.py

    Will result in:

    Traceback (most recent call last):
      File "/usr/local/lib/python3.6/asyncio/base_events.py", line 1043, in create_server
        sock.bind(sa)
    OSError: [Errno 99] Cannot assign requested address
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/src/test_ipv6.py", line 11, in <module>
        server = loop.run_until_complete(server_creation)
      File "/usr/local/lib/python3.6/asyncio/base_events.py", line 466, in run_until_complete
        return future.result()
      File "/usr/local/lib/python3.6/asyncio/base_events.py", line 1047, in create_server
        % (sa, err.strerror.lower()))
    OSError: [Errno 99] error while attempting to bind on address ('::1', 27015, 0, 0): cannot assign requested address

    By default Docker containers have only IPv4 enabled:

    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    valid_lft forever preferred_lft forever
    38: eth0@if39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
    valid_lft forever preferred_lft forever

    I believe this detection mechanism should rely on the interface requested. I found this on the web for Python 2 that manage to get the info per interface: https://pastebin.com/VEnhF1Ht but it's using an external library.

    However if you change the hostname to 127.0.0.1 it works normally.

    @cecton cecton mannequin added topic-asyncio type-bug An unexpected behavior, bug, or error labels Jul 17, 2017
    @pitrou
    Copy link
    Member

    pitrou commented Jul 19, 2017

    Better than trying to detect IPv6 compatibility beforehand would probably to recognize the error and simply ignore it.

    Note: errno 99 is EADDRNOTAVAIL.

    Something like this could work (untested):

    diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
    index 33b8f48..413161a 100644
    --- a/Lib/asyncio/base_events.py
    +++ b/Lib/asyncio/base_events.py
    @@ -1038,6 +1038,11 @@ class BaseEventLoop(events.AbstractEventLoop):
                         try:
                             sock.bind(sa)
                         except OSError as err:
    +                        if err.errno == errno.EADDRNOTAVAIL:
    +                            # See bpo-30945
    +                            sockets.pop()
    +                            sock.close()
    +                            continue
                             raise OSError(err.errno, 'error while attempting '
                                           'to bind on address %r: %s'
                                           % (sa, err.strerror.lower())) from None

    @pitrou pitrou added the 3.7 (EOL) end of life label Jul 19, 2017
    @1st1
    Copy link
    Member

    1st1 commented Jul 19, 2017

    Better than trying to detect IPv6 compatibility beforehand would probably to recognize the error and simply ignore it.

    +1

    @pitrou
    Copy link
    Member

    pitrou commented Jul 20, 2017

    Cécile, could you try the following patch? I have no easy way to test here.

    @cecton
    Copy link
    Mannequin Author

    cecton mannequin commented Jul 20, 2017

    Sure! It seems to work, the process returns an exit code of 0 and I see no traceback but the message is still displayed in the terminal.

    (Also I did something weird because your patch applies on branch master and I ran it with Python 3.6... I suppose it shouldn't be a problem)

    [0] [11:54:13] /tmp > d run -it --rm -v /tmp:/tmp:ro -v ~/repos/cpython/Lib/asyncio:/usr/local/lib/python3.6/asyncio:ro python:3.6 python /tmp/test_ipv6.py
    error while attempting to bind on address ('::1', 27015, 0, 0): cannot assign requested address
    [0] [11:54:19] /tmp > d run -it --rm -v /tmp:/tmp:ro python:3.6 python /tmp/test_ipv6.py
    Traceback (most recent call last):
      File "/usr/local/lib/python3.6/asyncio/base_events.py", line 1043, in create_server
        sock.bind(sa)
    OSError: [Errno 99] Cannot assign requested address
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/tmp/test_ipv6.py", line 11, in <module>
        server = loop.run_until_complete(server_creation)
      File "/usr/local/lib/python3.6/asyncio/base_events.py", line 466, in run_until_complete
        return future.result()
      File "/usr/local/lib/python3.6/asyncio/base_events.py", line 1047, in create_server
        % (sa, err.strerror.lower()))
    OSError: [Errno 99] error while attempting to bind on address ('::1', 27015, 0, 0): cannot assign requested address
    [1] [11:54:52] /tmp >

    @pitrou
    Copy link
    Member

    pitrou commented Jul 20, 2017

    Cécile, thank you. The reason the message is still displayed is that I turned the error into a warning (in other words, it is expected).

    @asottile
    Copy link
    Mannequin

    asottile mannequin commented Feb 9, 2018

    Seeing this as well when running the cpython test suite in docker:

    $ ./python -m test.test_asyncio
    
    ...
    
    [18 similar traces omitted]
    ======================================================================
    ERROR: test_sock_sendfile_zero_size (test.test_asyncio.test_unix_events.SelectorEventLoopUnixSockSendfileTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/code/Lib/test/test_asyncio/test_unix_events.py", line 559, in test_sock_sendfile_zero_size
        sock, proto = self.prepare()
      File "/code/Lib/test/test_asyncio/test_unix_events.py", line 483, in prepare
        lambda: proto, support.HOST, port))
      File "/code/Lib/test/test_asyncio/test_unix_events.py", line 476, in run_loop
        return self.loop.run_until_complete(coro)
      File "/code/Lib/asyncio/base_events.py", line 566, in run_until_complete
        return future.result()
      File "/code/Lib/asyncio/base_events.py", line 1346, in create_server
        % (sa, err.strerror.lower())) from None
    OSError: [Errno 99] error while attempting to bind on address ('::1', 39527, 0, 0): cannot assign requested address
    
    ----------------------------------------------------------------------
    
    

    I'm going to try and write a patch to skip these tests (there's already a helper)

    @asottile
    Copy link
    Mannequin

    asottile mannequin commented Feb 9, 2018

    Actually, my issue seems to be something more strange.

    The host being passed in is localhost which resolves to:

    >>> pprint.pprint(socket.getaddrinfo('localhost', 80))
    [(<AddressFamily.AF_INET: 2>,
      <SocketKind.SOCK_STREAM: 1>,
      6,
      '',
      ('127.0.0.1', 80)),
     (<AddressFamily.AF_INET: 2>,
      <SocketKind.SOCK_DGRAM: 2>,
      17,
      '',
      ('127.0.0.1', 80)),
     (<AddressFamily.AF_INET: 2>,
      <SocketKind.SOCK_RAW: 3>,
      0,
      '',
      ('127.0.0.1', 80)),
     (<AddressFamily.AF_INET6: 10>,
      <SocketKind.SOCK_STREAM: 1>,
      6,
      '',
      ('::1', 80, 0, 0)),
     (<AddressFamily.AF_INET6: 10>,
      <SocketKind.SOCK_DGRAM: 2>,
      17,
      '',
      ('::1', 80, 0, 0)),
     (<AddressFamily.AF_INET6: 10>,
      <SocketKind.SOCK_RAW: 3>,
      0,
      '',
      ('::1', 80, 0, 0))]
    

    asyncio is picking ipv6 because of this code:

    # Disable IPv4/IPv6 dual stack support (enabled by
    # default on Linux) which makes a single socket
    # listen on both address families.
    if af == AF_INET6 and hasattr(socket, 'IPPROTO_IPV6'):
    sock.setsockopt(socket.IPPROTO_IPV6,
    socket.IPV6_V6ONLY,
    True)

    despite my host not actually having an ipv6 network hooked up.

    @asottile
    Copy link
    Mannequin

    asottile mannequin commented Feb 9, 2018

    Applying this patch makes the tests pass for me, but I don't think the patch is appropriate (just hides the bug):

    $ git diff
    diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py
    index 5bd76d3..ff6c4e1 100644
    --- a/Lib/test/test_asyncio/test_unix_events.py
    +++ b/Lib/test/test_asyncio/test_unix_events.py
    @@ -480,7 +480,7 @@ class SelectorEventLoopUnixSockSendfileTests(test_utils.TestCase):
             proto = self.MyProto(self.loop)
             port = support.find_unused_port()
             server = self.run_loop(self.loop.create_server(
    -            lambda: proto, support.HOST, port))
    +            lambda: proto, support.HOSTv4, port))
             self.run_loop(self.loop.sock_connect(sock, (support.HOST, port)))
     
             def cleanup():
    

    @cecton
    Copy link
    Mannequin Author

    cecton mannequin commented Feb 10, 2018

    I see that the patch hasn't been applied to master on GitHub. Is there anything else expected from me on this ticket?

    @1st1
    Copy link
    Member

    1st1 commented May 28, 2018

    Does anybody wants to make a PR to fix this?

    @1st1
    Copy link
    Member

    1st1 commented May 29, 2018

    asyncio is picking ipv6 because of this code:

    # Disable IPv4/IPv6 dual stack support (enabled by
    # default on Linux) which makes a single socket
    # listen on both address families.
    if af == AF_INET6 and hasattr(socket, 'IPPROTO_IPV6'):
    sock.setsockopt(socket.IPPROTO_IPV6,
    socket.IPV6_V6ONLY,
    True)

    despite my host not actually having an ipv6 network hooked up.

    Which should be fine; create_server enumerates all addresses and tries to connect to each one. IPV6_V6ONLY is only applied to one socket for one IPv6 address. I think Antoine's patch is OK.

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @ezio-melotti ezio-melotti moved this to Todo in asyncio Jul 17, 2022
    @kumaraditya303 kumaraditya303 removed the 3.7 (EOL) end of life label Oct 18, 2022
    serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this issue Jan 22, 2024
    serhiy-storchaka added a commit that referenced this issue Jan 22, 2024
    miss-islington pushed a commit to miss-islington/cpython that referenced this issue Jan 22, 2024
    …reate_server() (pythonGH-114420)
    
    (cherry picked from commit a53e56e)
    
    Co-authored-by: Serhiy Storchaka <[email protected]>
    Co-authored-by: Antoine Pitrou <[email protected]>
    miss-islington pushed a commit to miss-islington/cpython that referenced this issue Jan 22, 2024
    …reate_server() (pythonGH-114420)
    
    (cherry picked from commit a53e56e)
    
    Co-authored-by: Serhiy Storchaka <[email protected]>
    Co-authored-by: Antoine Pitrou <[email protected]>
    @serhiy-storchaka
    Copy link
    Member

    Thank you for your patch Antoine. I am surprised that it was not merged at its time. It fixes a constant failure on some buildbots. Buildbot failure could perhaps be fixed by changing the configuration (I think that I encountered such errors on different buildbots in the past), but it is better to fix it in the core.

    @github-project-automation github-project-automation bot moved this from Todo to Done in asyncio Jan 22, 2024
    serhiy-storchaka added a commit that referenced this issue Jan 22, 2024
    …create_server() (GH-114420) (GH-114441)
    
    (cherry picked from commit a53e56e)
    
    Co-authored-by: Serhiy Storchaka <[email protected]>
    Co-authored-by: Antoine Pitrou <[email protected]>
    serhiy-storchaka added a commit that referenced this issue Jan 22, 2024
    …create_server() (GH-114420) (GH-114442)
    
    (cherry picked from commit a53e56e)
    
    Co-authored-by: Serhiy Storchaka <[email protected]>
    Co-authored-by: Antoine Pitrou <[email protected]>
    aisk pushed a commit to aisk/cpython that referenced this issue Feb 11, 2024
    Glyphack pushed a commit to Glyphack/cpython that referenced this issue Sep 2, 2024
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    topic-asyncio type-bug An unexpected behavior, bug, or error
    Projects
    Status: Done
    Development

    No branches or pull requests

    4 participants