Hello community, here is the log from the commit of package python-zeroconf for openSUSE:Factory checked in at 2020-12-21 10:23:35 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-zeroconf (Old) and /work/SRC/openSUSE:Factory/.python-zeroconf.new.5145 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-zeroconf" Mon Dec 21 10:23:35 2020 rev:17 rq:856970 version:0.28.7 Changes: -------- --- /work/SRC/openSUSE:Factory/python-zeroconf/python-zeroconf.changes 2020-11-26 23:16:09.977073587 +0100 +++ /work/SRC/openSUSE:Factory/.python-zeroconf.new.5145/python-zeroconf.changes 2020-12-21 10:26:38.356199055 +0100 @@ -1,0 +2,8 @@ +Sat Dec 19 10:34:17 UTC 2020 - Dirk Müller <dmuel...@suse.com> + +- update to 0.28.7: + * Fixed the IPv6 address rendering in the browser example, thanks to Alexey Vazhnov. + * Fixed a crash happening when a service is added or removed during handle_response + and improved exception handling, thanks to J. Nick Koston. + +------------------------------------------------------------------- Old: ---- python-zeroconf-0.28.6.tar.gz New: ---- python-zeroconf-0.28.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-zeroconf.spec ++++++ --- /var/tmp/diff_new_pack.m8bn4g/_old 2020-12-21 10:26:38.992199776 +0100 +++ /var/tmp/diff_new_pack.m8bn4g/_new 2020-12-21 10:26:39.000199785 +0100 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-zeroconf -Version: 0.28.6 +Version: 0.28.7 Release: 0 Summary: Pure Python Multicast DNS Service Discovery Library (Bonjour/Avahi compatible) License: LGPL-2.0-only ++++++ python-zeroconf-0.28.6.tar.gz -> python-zeroconf-0.28.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.28.6/README.rst new/python-zeroconf-0.28.7/README.rst --- old/python-zeroconf-0.28.6/README.rst 2020-10-13 20:09:25.000000000 +0200 +++ new/python-zeroconf-0.28.7/README.rst 2020-12-13 02:08:20.000000000 +0100 @@ -134,6 +134,13 @@ Changelog ========= +0.28.7 +====== + +* Fixed the IPv6 address rendering in the browser example, thanks to Alexey Vazhnov. +* Fixed a crash happening when a service is added or removed during handle_response + and improved exception handling, thanks to J. Nick Koston. + 0.28.6 ====== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.28.6/examples/browser.py new/python-zeroconf-0.28.7/examples/browser.py --- old/python-zeroconf-0.28.6/examples/browser.py 2020-10-13 20:09:25.000000000 +0200 +++ new/python-zeroconf-0.28.7/examples/browser.py 2020-12-13 02:08:20.000000000 +0100 @@ -7,7 +7,6 @@ import argparse import logging -import socket from time import sleep from typing import cast @@ -23,7 +22,7 @@ info = zeroconf.get_service_info(service_type, name) print("Info from zeroconf.get_service_info: %r" % (info)) if info: - addresses = ["%s:%d" % (socket.inet_ntoa(addr), cast(int, info.port)) for addr in info.addresses] + addresses = ["%s:%d" % (addr, cast(int, info.port)) for addr in info.parsed_addresses()] print(" Addresses: %s" % ", ".join(addresses)) print(" Weight: %d, priority: %d" % (info.weight, info.priority)) print(" Server: %s" % (info.server,)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.28.6/zeroconf/__init__.py new/python-zeroconf-0.28.7/zeroconf/__init__.py --- old/python-zeroconf-0.28.6/zeroconf/__init__.py 2020-10-13 20:09:25.000000000 +0200 +++ new/python-zeroconf-0.28.7/zeroconf/__init__.py 2020-12-13 02:08:20.000000000 +0100 @@ -42,7 +42,7 @@ __author__ = 'Paul Scott-Murphy, William McBrine' __maintainer__ = 'Jakub Stasiak <ja...@stasiak.at>' -__version__ = '0.28.6' +__version__ = '0.28.7' __license__ = 'LGPL' @@ -381,6 +381,10 @@ pass +class ServiceNameAlreadyRegistered(Error): + pass + + # implementation classes @@ -2341,6 +2345,96 @@ return cast(bool, addr.version == 6 if sock.family == socket.AF_INET6 else addr.version == 4) +class ServiceRegistry: + """A registry to keep track of services. + + This class exists to ensure services can + be safely added and removed with thread + safety. + """ + + def __init__( + self, + ) -> None: + """Create the ServiceRegistry class.""" + self.services = {} # type: Dict[str, ServiceInfo] + self.types = {} # type: Dict[str, List] + self.servers = {} # type: Dict[str, List] + self._lock = threading.Lock() # add and remove services thread safe + + def add(self, info: ServiceInfo) -> None: + """Add a new service to the registry.""" + + with self._lock: + self._add(info) + + def remove(self, info: ServiceInfo) -> None: + """Remove a new service from the registry.""" + + with self._lock: + self._remove(info) + + def update(self, info: ServiceInfo) -> None: + """Update new service in the registry.""" + + with self._lock: + self._remove(info) + self._add(info) + + def get_service_infos(self) -> List[ServiceInfo]: + """Return all ServiceInfo.""" + return list(self.services.values()) + + def get_info_name(self, name: str) -> Optional[ServiceInfo]: + """Return all ServiceInfo for the name.""" + return self.services.get(name) + + def get_types(self) -> List[str]: + """Return all types.""" + return list(self.types.keys()) + + def get_infos_type(self, type_: str) -> List[ServiceInfo]: + """Return all ServiceInfo matching type.""" + return self._get_by_index("types", type_) + + def get_infos_server(self, server: str) -> List[ServiceInfo]: + """Return all ServiceInfo matching server.""" + return self._get_by_index("servers", server) + + def _get_by_index(self, attr: str, key: str) -> List[ServiceInfo]: + """Return all ServiceInfo matching the index.""" + service_infos = [] + + for name in getattr(self, attr).get(key, [])[:]: + info = self.services.get(name) + # Since we do not get under a lock since it would be + # a performance issue, its possible + # the service can be unregistered during the get + # so we must check if info is None + if info is not None: + service_infos.append(info) + + return service_infos + + def _add(self, info: ServiceInfo) -> None: + """Add a new service under the lock.""" + lower_name = info.name.lower() + if lower_name in self.services: + raise ServiceNameAlreadyRegistered + + self.services[lower_name] = info + self.types.setdefault(info.type, []).append(lower_name) + self.servers.setdefault(info.server, []).append(lower_name) + + def _remove(self, info: ServiceInfo) -> None: + """Remove a service under the lock.""" + lower_name = info.name.lower() + old_service_info = self.services[lower_name] + self.types[old_service_info.type].remove(lower_name) + self.servers[old_service_info.server].remove(lower_name) + del self.services[lower_name] + + class Zeroconf(QuietLogger): """Implementation of Zeroconf Multicast DNS Service Discovery @@ -2398,8 +2492,7 @@ self.listeners = [] # type: List[RecordUpdateListener] self.browsers = {} # type: Dict[ServiceListener, ServiceBrowser] - self.services = {} # type: Dict[str, ServiceInfo] - self.servicetypes = {} # type: Dict[str, int] + self.registry = ServiceRegistry() self.cache = DNSCache() @@ -2487,27 +2580,18 @@ info.host_ttl = ttl info.other_ttl = ttl self.check_service(info, allow_name_change, cooperating_responders) - self.services[info.name.lower()] = info - if info.type in self.servicetypes: - self.servicetypes[info.type] += 1 - else: - self.servicetypes[info.type] = 1 - - self._broadcast_service(info) + self.registry.add(info) + self._broadcast_service(info, _REGISTER_TIME, None) def update_service(self, info: ServiceInfo) -> None: """Registers service information to the network with a default TTL. Zeroconf will then respond to requests for information for that service.""" - assert self.services[info.name.lower()] is not None - - self.services[info.name.lower()] = info - - self._broadcast_service(info) - - def _broadcast_service(self, info: ServiceInfo) -> None: + self.registry.update(info) + self._broadcast_service(info, _REGISTER_TIME, None) + def _broadcast_service(self, info: ServiceInfo, interval: int, ttl: Optional[int]) -> None: now = current_time_millis() next_time = now i = 0 @@ -2516,44 +2600,51 @@ self.wait(next_time - now) now = current_time_millis() continue + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) - out.add_answer_at_time(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, info.other_ttl, info.name), 0) - out.add_answer_at_time( - DNSService( - info.name, - _TYPE_SRV, - _CLASS_IN | _CLASS_UNIQUE, - info.host_ttl, - info.priority, - info.weight, - cast(int, info.port), - info.server, - ), - 0, - ) + self._add_broadcast_answer(out, info, ttl) + self.send(out) + i += 1 + next_time += interval + + def _add_broadcast_answer(self, out: DNSOutgoing, info: ServiceInfo, override_ttl: Optional[int]) -> None: + """Add answers to broadcast a service.""" + other_ttl = info.other_ttl if override_ttl is None else override_ttl + host_ttl = info.host_ttl if override_ttl is None else override_ttl + out.add_answer_at_time(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, other_ttl, info.name), 0) + out.add_answer_at_time( + DNSService( + info.name, + _TYPE_SRV, + _CLASS_IN | _CLASS_UNIQUE, + host_ttl, + info.priority, + info.weight, + cast(int, info.port), + info.server, + ), + 0, + ) + out.add_answer_at_time( + DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, other_ttl, info.text), 0 + ) + for address in info.addresses_by_version(IPVersion.All): + type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A out.add_answer_at_time( - DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, info.other_ttl, info.text), 0 + DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE, host_ttl, address), 0 ) - for address in info.addresses_by_version(IPVersion.All): - type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A - out.add_answer_at_time( - DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE, info.host_ttl, address), 0 - ) - self.send(out) - i += 1 - next_time += _REGISTER_TIME def unregister_service(self, info: ServiceInfo) -> None: """Unregister a service.""" - try: - del self.services[info.name.lower()] - if self.servicetypes[info.type] > 1: - self.servicetypes[info.type] -= 1 - else: - del self.servicetypes[info.type] - except Exception as e: # TODO stop catching all Exceptions - log.exception('Unknown error, possibly benign: %r', e) + self.registry.remove(info) + self._broadcast_service(info, _UNREGISTER_TIME, 0) + + def unregister_all_services(self) -> None: + """Unregister all registered services.""" + service_infos = self.registry.get_service_infos() + if not service_infos: + return now = current_time_millis() next_time = now i = 0 @@ -2563,70 +2654,12 @@ now = current_time_millis() continue out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) - out.add_answer_at_time(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) - out.add_answer_at_time( - DNSService( - info.name, - _TYPE_SRV, - _CLASS_IN | _CLASS_UNIQUE, - 0, - info.priority, - info.weight, - cast(int, info.port), - info.name, - ), - 0, - ) - out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, 0, info.text), 0) - - for address in info.addresses_by_version(IPVersion.All): - type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A - out.add_answer_at_time( - DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE, 0, address), 0 - ) + for info in service_infos: + self._add_broadcast_answer(out, info, 0) self.send(out) i += 1 next_time += _UNREGISTER_TIME - def unregister_all_services(self) -> None: - """Unregister all registered services.""" - if len(self.services) > 0: - now = current_time_millis() - next_time = now - i = 0 - while i < 3: - if now < next_time: - self.wait(next_time - now) - now = current_time_millis() - continue - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) - for info in self.services.values(): - out.add_answer_at_time(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) - out.add_answer_at_time( - DNSService( - info.name, - _TYPE_SRV, - _CLASS_IN | _CLASS_UNIQUE, - 0, - info.priority, - info.weight, - cast(int, info.port), - info.server, - ), - 0, - ) - out.add_answer_at_time( - DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, 0, info.text), 0 - ) - for address in info.addresses_by_version(IPVersion.All): - type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A - out.add_answer_at_time( - DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE, 0, address), 0 - ) - self.send(out) - i += 1 - next_time += _UNREGISTER_TIME - def check_service( self, info: ServiceInfo, allow_name_change: bool, cooperating_responders: bool = False ) -> None: @@ -2768,124 +2801,124 @@ for question in msg.questions: if question.type == _TYPE_PTR: if question.name == "_services._dns-sd._udp.local.": - for stype in self.servicetypes.keys(): + for stype in self.registry.get_types(): if out is None: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.add_answer( msg, DNSPointer( - "_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_OTHER_TTL, stype + "_services._dns-sd._udp.local.", + _TYPE_PTR, + _CLASS_IN, + _DNS_OTHER_TTL, + stype, ), ) - for service in self.services.values(): - if question.name == service.type: - if out is None: - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) - out.add_answer( - msg, - DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, service.other_ttl, service.name), - ) + for service in self.registry.get_infos_type(question.name): + if out is None: + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.add_answer( + msg, + DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, service.other_ttl, service.name), + ) - # Add recommended additional answers according to - # https://tools.ietf.org/html/rfc6763#section-12.1. - out.add_additional_answer( - DNSService( - service.name, - _TYPE_SRV, - _CLASS_IN | _CLASS_UNIQUE, - service.host_ttl, - service.priority, - service.weight, - cast(int, service.port), - service.server, - ) + # Add recommended additional answers according to + # https://tools.ietf.org/html/rfc6763#section-12.1. + out.add_additional_answer( + DNSService( + service.name, + _TYPE_SRV, + _CLASS_IN | _CLASS_UNIQUE, + service.host_ttl, + service.priority, + service.weight, + cast(int, service.port), + service.server, ) + ) + out.add_additional_answer( + DNSText( + service.name, + _TYPE_TXT, + _CLASS_IN | _CLASS_UNIQUE, + service.other_ttl, + service.text, + ) + ) + for address in service.addresses_by_version(IPVersion.All): + type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A out.add_additional_answer( - DNSText( - service.name, - _TYPE_TXT, + DNSAddress( + service.server, + type_, _CLASS_IN | _CLASS_UNIQUE, - service.other_ttl, - service.text, + service.host_ttl, + address, ) ) + else: + if out is None: + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + + name_to_find = question.name.lower() + + # Answer A record queries for any service addresses we know + if question.type in (_TYPE_A, _TYPE_ANY): + for service in self.registry.get_infos_server(name_to_find): for address in service.addresses_by_version(IPVersion.All): type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A - out.add_additional_answer( + out.add_answer( + msg, DNSAddress( - service.server, + question.name, type_, _CLASS_IN | _CLASS_UNIQUE, service.host_ttl, address, - ) + ), ) - else: - try: - if out is None: - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) - # Answer A record queries for any service addresses we know - if question.type in (_TYPE_A, _TYPE_ANY): - for service in self.services.values(): - if service.server == question.name.lower(): - for address in service.addresses_by_version(IPVersion.All): - type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A - out.add_answer( - msg, - DNSAddress( - question.name, - type_, - _CLASS_IN | _CLASS_UNIQUE, - service.host_ttl, - address, - ), - ) - - name_to_find = question.name.lower() - if name_to_find not in self.services: - continue - service = self.services[name_to_find] + service = self.registry.get_info_name(name_to_find) # type: ignore + if service is None: + continue - if question.type in (_TYPE_SRV, _TYPE_ANY): - out.add_answer( - msg, - DNSService( - question.name, - _TYPE_SRV, - _CLASS_IN | _CLASS_UNIQUE, - service.host_ttl, - service.priority, - service.weight, - cast(int, service.port), + if question.type in (_TYPE_SRV, _TYPE_ANY): + out.add_answer( + msg, + DNSService( + question.name, + _TYPE_SRV, + _CLASS_IN | _CLASS_UNIQUE, + service.host_ttl, + service.priority, + service.weight, + cast(int, service.port), + service.server, + ), + ) + if question.type in (_TYPE_TXT, _TYPE_ANY): + out.add_answer( + msg, + DNSText( + question.name, + _TYPE_TXT, + _CLASS_IN | _CLASS_UNIQUE, + service.other_ttl, + service.text, + ), + ) + if question.type == _TYPE_SRV: + for address in service.addresses_by_version(IPVersion.All): + type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A + out.add_additional_answer( + DNSAddress( service.server, - ), - ) - if question.type in (_TYPE_TXT, _TYPE_ANY): - out.add_answer( - msg, - DNSText( - question.name, - _TYPE_TXT, + type_, _CLASS_IN | _CLASS_UNIQUE, - service.other_ttl, - service.text, - ), - ) - if question.type == _TYPE_SRV: - for address in service.addresses_by_version(IPVersion.All): - type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A - out.add_additional_answer( - DNSAddress( - service.server, - type_, - _CLASS_IN | _CLASS_UNIQUE, - service.host_ttl, - address, - ) + service.host_ttl, + address, ) - except Exception: # TODO stop catching all Exceptions - self.log_exception_warning() + ) if out is not None and out.answers: out.id = msg.id diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.28.6/zeroconf/test.py new/python-zeroconf-0.28.7/zeroconf/test.py --- old/python-zeroconf-0.28.6/zeroconf/test.py 2020-10-13 20:09:25.000000000 +0200 +++ new/python-zeroconf-0.28.7/zeroconf/test.py 2020-12-13 02:08:20.000000000 +0100 @@ -878,6 +878,43 @@ nbr_answers = nbr_additionals = nbr_authorities = 0 +class TestServiceRegistry(unittest.TestCase): + def test_only_register_once(self): + type_ = "_test-srvc-type._tcp.local." + name = "xxxyyy" + registration_name = "%s.%s" % (name, type_) + + desc = {'path': '/~paulsm/'} + info = ServiceInfo( + type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")] + ) + + registry = r.ServiceRegistry() + registry.add(info) + self.assertRaises(r.ServiceNameAlreadyRegistered, registry.add, info) + registry.remove(info) + registry.add(info) + + def test_lookups(self): + type_ = "_test-srvc-type._tcp.local." + name = "xxxyyy" + registration_name = "%s.%s" % (name, type_) + + desc = {'path': '/~paulsm/'} + info = ServiceInfo( + type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")] + ) + + registry = r.ServiceRegistry() + registry.add(info) + + assert registry.get_service_infos() == [info] + assert registry.get_info_name(registration_name) == info + assert registry.get_infos_type(type_) == [info] + assert registry.get_infos_server("ash-2.local.") == [info] + assert registry.get_types() == [type_] + + class TestDNSCache(unittest.TestCase): def test_order(self): record1 = r.DNSAddress('a', r._TYPE_SOA, r._CLASS_IN, 1, b'a') _______________________________________________ openSUSE Commits mailing list -- commit@lists.opensuse.org To unsubscribe, email commit-le...@lists.opensuse.org List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette List Archives: https://lists.opensuse.org/archives/list/commit@lists.opensuse.org