Skip to content

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

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

Open
agners opened this issue Feb 7, 2024 · 10 comments

Comments

@agners
Copy link
Collaborator

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
Collaborator 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

@agners
Copy link
Collaborator 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
Collaborator 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

@agners
Copy link
Collaborator Author

agners commented Apr 16, 2025

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 101] Network is unreachable

It seems that this is more related to the routing table, e.g. adding a route like this makes the error go away:

ip -6 route add table local multicast ff00::/8 dev lo proto kernel metric 255 pref medium

There is also an explicit flag which allows to turn on/off multicast on the loopback interface (by default it is off):

ip link set lo multicast on 

With ifaddr/ifaddr#59 ifaddr will return the current state of that flag per interface.

However, in my testing using Arch Linux with Linux 6.14.2, even with multicast set to on, multicast is not working on the loopback interface (I don't see multicast packets from the same or other processes). It does work with a dummy interface though.

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