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 <[email protected]>
+
+- 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 <[email protected]>'
-__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 -- [email protected]
To unsubscribe, email [email protected]
List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette
List Archives:
https://lists.opensuse.org/archives/list/[email protected]