frawau / aiozeroconf

An asyncio/pure python implementation of multicast DNS service discovery
GNU Lesser General Public License v2.1
25 stars 6 forks source link

Error when setting up on lo0 #14

Open robbiet480 opened 5 years ago

robbiet480 commented 5 years ago

I initialize aiozeroconf like this:

from aiozeroconf import Zeroconf, ServiceInfo

zeroconf_name = '{}.{}'.format(hass.config.location_name, ZEROCONF_TYPE)

params = {
    'version': __version__,
    'base_url': hass.config.api.base_url,
    # always needs authentication
    'requires_api_password': True,
}

info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name,
                   port=hass.http.server_port, properties=params)

zeroconf = Zeroconf(hass.loop)

await zeroconf.register_service(info)

I get this error upon launching (I slightly changed the Zeroconf.initialize function to set up interfaces one by one to isolate what went wrong): [aiozeroconf.aiozeroconf] initializing comm af 30 on interfaces ['lo0']: [Errno 49] Can't assign requested address.

Shortly after startup I get these errors too:

2019-05-15 14:27:17 ERROR (MainThread) [homeassistant.core] Error doing job: Exception in callback _SelectorDatagramTransport._read_ready()
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/selector_events.py", line 962, in _read_ready
    self._protocol.datagram_received(data, addr)
  File "/Users/robbiet480/Repos/HomeAssistant/home-assistant/venv/lib/python3.7/site-packages/aiozeroconf/aiozeroconf.py", line 1115, in datagram_received
    self.zc.handle_query(msg, resp_addr, _MDNS_PORT)
  File "/Users/robbiet480/Repos/HomeAssistant/home-assistant/venv/lib/python3.7/site-packages/aiozeroconf/aiozeroconf.py", line 2081, in handle_query
    self.send(out, addr, port)
  File "/Users/robbiet480/Repos/HomeAssistant/home-assistant/venv/lib/python3.7/site-packages/aiozeroconf/aiozeroconf.py", line 2085, in send
    packet = out.packet()
  File "/Users/robbiet480/Repos/HomeAssistant/home-assistant/venv/lib/python3.7/site-packages/aiozeroconf/aiozeroconf.py", line 998, in packet
    overrun_additionals += self.write_record(additional, 0)
  File "/Users/robbiet480/Repos/HomeAssistant/home-assistant/venv/lib/python3.7/site-packages/aiozeroconf/aiozeroconf.py", line 966, in write_record
    record.write(self)
  File "/Users/robbiet480/Repos/HomeAssistant/home-assistant/venv/lib/python3.7/site-packages/aiozeroconf/aiozeroconf.py", line 489, in write
    out.write_string(self.address)
  File "/Users/robbiet480/Repos/HomeAssistant/home-assistant/venv/lib/python3.7/site-packages/aiozeroconf/aiozeroconf.py", line 872, in write_string
    assert isinstance(value, bytes)
AssertionError

I'm on macOS 10.14.4.

$ ifconfig 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>
robbiet480 commented 5 years ago

It looks like that error Error doing job: Exception in callback _SelectorDatagramTransport._read_ready() is related to me setting up interfaces one at a time to debug, so don't worry about that one.

frawau commented 5 years ago

I won't worry ;) In any case, using this library on lo0 is probably not really useful.

robbiet480 commented 5 years ago

@frawau Sure, but it throws a big red error message that's going to confuse our users. Can you either filter out that error message for lo0 or lower the log level from error to something not as scary?

robbiet480 commented 5 years ago

I can also submit the PR to not throw the error on lo0 if you want.

frawau commented 5 years ago

Please do. I am one of the organisers of PyCon Thailand, and really busy right now.

balloob commented 5 years ago

I dug more into this and this is what is happening:

aiozeroconf will detect it's own broadcasted service on each interface, and for some interfaces this is adddress will be None. Example:

ServiceInfo(type='_home-assistant._tcp.local.', name='Home._home-assistant._tcp.local.', address=None, port=8123, weight=0, priority=0, server='Home._home-assistant._tcp.local.', properties={'version': '0.94.0.dev0', 'base_url': 'http://192.168.1.234:8123', 'requires_api_password': True})

balloob commented 5 years ago

Adding an interface ban list is not going to help, as we won't know in advance which interfaces will misbehave. (I have multiple that misbehave)

balloob commented 5 years ago

This patch hides the error. I don't know if it hides an underlying issue though.

diff --git a/aiozeroconf/aiozeroconf.py b/aiozeroconf/aiozeroconf.py
index 8662c60..e1711b8 100644
--- a/aiozeroconf/aiozeroconf.py
+++ b/aiozeroconf/aiozeroconf.py
@@ -2068,7 +2068,8 @@ class Zeroconf(QuietLogger):
                         out.add_answer(msg, DNSText(
                             question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE,
                             _DNS_TTL, service.text))
-                    if question.type == _TYPE_SRV:
+                    if (question.type == _TYPE_SRV and
+                            service.address is not None):
                         out.add_additional_answer(DNSAddress(
                             service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE,
                             _DNS_TTL, service.address))
frawau commented 5 years ago

Hi Paulus,

I had a look, and I am still not entirely sure what is going on. But I think I have a simple workaround.

Do something like

info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name, address=b"", port=8123, properties=params)

For services you register. Just adding the address with an empty byte string.

I think it has to do with the fact that when receiving a "A" or "SRV" query, it should respond with the address of the interface on which the query was received, and that should be provided in the ServiceInfo.

But I am not entirely sure....

Regards, François

balloob commented 5 years ago

If I add an empty byte string, I end up breaking a zeroconf consumer that I had running, which is trying to convert the address to an IP address using the ipaddress package in Python. So I think that it is a good indicator that passing an empty byte string is not a good idea.

I noticed this PR on python-zeroconf in which they behave the same as with my diff above: not forward any DNSAddress.

I don't know enough about zeroconf specifics, but isn't it weird that the code registering a service needs to know the IP address on each interface ?

frawau commented 5 years ago

From the look of it, that PR adds "addresses", a list, and creates a record for each address in the list. But it does look that you still have to provide the addresses when creating the ServiceInfo.

I don't disagree with you, we should be able to automatically add the address of the interface that advertises the service. I did not work with this code for quite some time, and I am busy with PyCon Thailand (remember? I did ask you uf you could be a keynote). Hopefully I will find sometime this WE.

Cheers

balloob commented 5 years ago

remember? I did ask you uf you could be a keynote

I remembered when I saw you mentioned it the first time 👍 Good luck with the conference !

frawau commented 5 years ago

Hi,

I have a test version in branch dev_ha. This handles multiple A and AAAA records. When publishing a service, if no address is specified, it will add A and AAAA records for all the addresses of all the interfaces being used.

Be sure to read the commit message. Breaking change.

Let me know how it works.

Regards, François