Skip to content
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

Using IPVersion.All leads to OSError: [Errno 101] Network is unreachable logged #1357

Open
agners opened this issue Feb 7, 2024 · 9 comments · May be fixed by #1358
Open

Using IPVersion.All leads to OSError: [Errno 101] Network is unreachable logged #1357

agners opened this issue Feb 7, 2024 · 9 comments · May be fixed by #1358

Comments

@agners
Copy link

agners commented Feb 7, 2024

When using AsyncZeroconf(ip_version=IPVersion.All) this can lead to the following warning being logged:

WARNING Error with socket 66 (('::1', 5353, 0, 0))): [Errno 101] Network is unreachable
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/asyncio/selector_events.py", line 1196, in sendto
    self._sock.sendto(data, addr)
OSError: [Errno 101] Network is unreachable

It seems that listening to the IPv6 loopback on it's own isn't problematic, but when trying to send to that socket, it leads to the above error. The problematic socket is created via get_all_addresses_v6(), which returns the loopback interface with the ::1 address (the full tuple being (('::1', 0, 0), 1)).

This then leads to a socket with the follow options created:

import socket
import struct
s = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 255)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, True)
s.bind(('::2', 5353, 0, 0))
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, struct.pack('@I', 1))

When this socket then is used, the stack trace appears:

s.sendto(b"Hello", ('ff02::fb', 5353, 0, 0))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 101] Network is unreachable

The relevant option seem to be the IPv6 specific binding to the interface index IPV6_MULTICAST_IF, in this case 1 for the loopback interface.

@agners
Copy link
Author

agners commented Feb 7, 2024

The problem here really is the missing multicast support flag on the loopback interface

$ ip addr                                                                                                                                                                                                                                                                                             
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
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever

E.g. a Ethernet interface has the flag:

2: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000

On IPv4 it is probably not a problem since the socket option IPV6_MULTICAST_IF to bind to a specific interface index is IPv6 specific.

I found that e.g. the Matter SDK's minimal mDNS implementation simply skips loopback (see https://github.com/project-chip/connectedhomeip/blob/v1.2.0.1/src/lib/dnssd/minimal_mdns/AddressPolicy_DefaultImpl.cpp#L41-L53).

However, it seems we can learn that from the interface flags instead. But it would require a change in the ifaddr library.

@bdraco
Copy link
Member

bdraco commented Feb 7, 2024

Looks like macos has MULTICAST on lo0

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
	options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
	inet 127.0.0.1 netmask 0xff000000
	inet6 ::1 prefixlen 128 
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
	nd6 options=201<PERFORMNUD,DAD>

@bdraco
Copy link
Member

bdraco commented Feb 7, 2024

linux 4.4.x

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:24830216 errors:0 dropped:0 overruns:0 frame:0
          TX packets:24830216 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:3042660938 (2.8 GiB)  TX bytes:3042660938 (2.8 GiB)

@bdraco
Copy link
Member

bdraco commented Feb 7, 2024

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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever

@bdraco
Copy link
Member

bdraco commented Feb 7, 2024

In HA we already explicitly exclude loopback

        zc_args["interfaces"] = [
            str(source_ip)
            for source_ip in await network.async_get_enabled_source_ips(hass)
            if not source_ip.is_loopback
            and not (isinstance(source_ip, IPv6Address) and source_ip.is_global)
            and not (
                isinstance(source_ip, IPv6Address)
                and zc_args["ip_version"] == IPVersion.V4Only
            )
            and not (
                isinstance(source_ip, IPv4Address)
                and zc_args["ip_version"] == IPVersion.V6Only
            )
        ]

@bdraco
Copy link
Member

bdraco commented Feb 7, 2024

There is even a docstring that loopback doesn't work.

Someone might need it though so I think we can change InterfaceChoice.All to exclude loopback, and add InterfaceChoice.AllWithLoopback

@bdraco bdraco linked a pull request Feb 7, 2024 that will close this issue
@agners
Copy link
Author

agners commented Feb 7, 2024

There is a iflags, I think it would be the better indication.

Other interfaces might have that restriction too. E.g. a manually created dummy device:

sudo ip link add name loop1 type dummy
sudo ip addr add ::2 dev loop1

@agners
Copy link
Author

agners commented Feb 7, 2024

This would allow to filter the interfaces smarter on our end: ifaddr/ifaddr#59

@bdraco
Copy link
Member

bdraco commented Feb 7, 2024

That would be better but realistically we don't have that information unless your PR gets merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants