Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-zeroconf for openSUSE:Factory
checked in at 2021-09-07 21:21:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-zeroconf (Old)
and /work/SRC/openSUSE:Factory/.python-zeroconf.new.1899 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-zeroconf"
Tue Sep 7 21:21:28 2021 rev:23 rq:917173 version:0.36.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-zeroconf/python-zeroconf.changes
2021-08-18 08:57:05.810891310 +0200
+++
/work/SRC/openSUSE:Factory/.python-zeroconf.new.1899/python-zeroconf.changes
2021-09-07 21:22:10.441357543 +0200
@@ -1,0 +2,26 @@
+Mon Sep 6 09:19:13 UTC 2021 - Antonio Larrosa <[email protected]>
+
+- Update to 0.36.2:
+ * Include NSEC records for non-existent types when responding
+ with addresses
+ * Implements RFC6762 sec 6.2
+ (http://datatracker.ietf.org/doc/html/rfc6762#section-6.2)
+
+- Update to 0.36.1:
+ * Skip goodbye packets for addresses when there is another
+ service registered with the same name (#968) @bdraco
+ * If a ServiceInfo that used the same server name as another
+ ServiceInfo was unregistered, goodbye packets would be sent for
+ the addresses and would cause the other service to be seen as
+ offline.
+ * Fixed equality and hash for dns records with the unique bit
+ (#969)
+ * These records should have the same hash and equality since
+ the unique bit (cache flush bit) is not considered when adding
+ or removing the records from the cache.
+
+- Update to 0.36.0:
+ * Technically backwards incompatible:
+ * Fill incomplete IPv6 tuples to avoid WinError on windows (#965)
+
+-------------------------------------------------------------------
Old:
----
python-zeroconf-0.35.1.tar.xz
New:
----
python-zeroconf-0.36.2.tar.xz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-zeroconf.spec ++++++
--- /var/tmp/diff_new_pack.IYQz1m/_old 2021-09-07 21:22:10.905358104 +0200
+++ /var/tmp/diff_new_pack.IYQz1m/_new 2021-09-07 21:22:10.909358109 +0200
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-zeroconf
-Version: 0.35.1
+Version: 0.36.2
Release: 0
Summary: Pure Python Multicast DNS Service Discovery Library
(Bonjour/Avahi compatible)
License: LGPL-2.0-only
++++++ _service ++++++
--- /var/tmp/diff_new_pack.IYQz1m/_old 2021-09-07 21:22:10.937358143 +0200
+++ /var/tmp/diff_new_pack.IYQz1m/_new 2021-09-07 21:22:10.941358147 +0200
@@ -2,8 +2,8 @@
<service name="obs_scm" mode="disabled">
<param name="url">https://github.com/jstasiak/python-zeroconf</param>
<param name="scm">git</param>
- <param name="revision">4281221b668123b770c6d6b0835dd876d1d2f22d</param>
- <param name="version">0.35.1</param>
+ <param name="revision">0.36.2</param>
+ <param name="version">0.36.2</param>
</service>
<service name="set_version" mode="disabled"/>
++++++ python-zeroconf-0.35.1.tar.xz -> python-zeroconf-0.36.2.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/README.rst
new/python-zeroconf-0.36.2/README.rst
--- old/python-zeroconf-0.35.1/README.rst 2021-08-15 21:14:10.000000000
+0200
+++ new/python-zeroconf-0.36.2/README.rst 2021-08-30 17:04:19.000000000
+0200
@@ -75,8 +75,6 @@
* `InterfaceChoice.All` is an alias for `InterfaceChoice.Default` on non-POSIX
systems.
-* On Windows specific interfaces can only be requested as interface indexes,
- not as IP addresses.
* Dual-stack IPv6 sockets are used, which may not be supported everywhere (some
BSD variants do not have them).
* Listening on localhost (`::1`) does not work. Help with understanding why is
@@ -140,6 +138,35 @@
Changelog
=========
+0.36.2
+======
+
+* Include NSEC records for non-existent types when responding with addresses
(#972) (#971) @bdraco
+ Implements RFC6762 sec 6.2
(http://datatracker.ietf.org/doc/html/rfc6762#section-6.2)
+
+0.36.1
+======
+
+* Skip goodbye packets for addresses when there is another service registered
with the same name (#968) @bdraco
+
+ If a ServiceInfo that used the same server name as another ServiceInfo
+ was unregistered, goodbye packets would be sent for the addresses and
+ would cause the other service to be seen as offline.
+* Fixed equality and hash for dns records with the unique bit (#969) @bdraco
+
+ These records should have the same hash and equality since
+ the unique bit (cache flush bit) is not considered when adding or removing
+ the records from the cache.
+
+0.36.0
+======
+
+Technically backwards incompatible:
+
+* Fill incomplete IPv6 tuples to avoid WinError on windows (#965) @lokesh2019
+
+ Fixed #932
+
0.35.1
======
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/setup.cfg
new/python-zeroconf-0.36.2/setup.cfg
--- old/python-zeroconf-0.35.1/setup.cfg 2021-08-15 21:14:10.000000000
+0200
+++ new/python-zeroconf-0.36.2/setup.cfg 2021-08-30 17:04:19.000000000
+0200
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 0.35.1
+current_version = 0.36.2
commit = True
tag = True
tag_name = {new_version}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-zeroconf-0.35.1/tests/services/test_registry.py
new/python-zeroconf-0.36.2/tests/services/test_registry.py
--- old/python-zeroconf-0.35.1/tests/services/test_registry.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/tests/services/test_registry.py 2021-08-30
17:04:19.000000000 +0200
@@ -28,6 +28,29 @@
registry.async_remove(info)
registry.async_add(info)
+ def test_register_same_server(self):
+ type_ = "_test-srvc-type._tcp.local."
+ name = "xxxyyy"
+ name2 = "xxxyyy2"
+ registration_name = "%s.%s" % (name, type_)
+ registration_name2 = "%s.%s" % (name2, type_)
+
+ desc = {'path': '/~paulsm/'}
+ info = ServiceInfo(
+ type_, registration_name, 80, 0, 0, desc, "same.local.",
addresses=[socket.inet_aton("10.0.1.2")]
+ )
+ info2 = ServiceInfo(
+ type_, registration_name2, 80, 0, 0, desc, "same.local.",
addresses=[socket.inet_aton("10.0.1.2")]
+ )
+ registry = r.ServiceRegistry()
+ registry.async_add(info)
+ registry.async_add(info2)
+ assert registry.async_get_infos_server("same.local.") == [info, info2]
+ registry.async_remove(info)
+ assert registry.async_get_infos_server("same.local.") == [info2]
+ registry.async_remove(info2)
+ assert registry.async_get_infos_server("same.local.") == []
+
def test_unregister_multiple_times(self):
"""Verify we can unregister a service multiple times.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/tests/test_asyncio.py
new/python-zeroconf-0.36.2/tests/test_asyncio.py
--- old/python-zeroconf-0.35.1/tests/test_asyncio.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/tests/test_asyncio.py 2021-08-30
17:04:19.000000000 +0200
@@ -174,6 +174,140 @@
@pytest.mark.asyncio
+async def test_async_service_registration_same_server_different_ports() ->
None:
+ """Test registering services with the same server with different srv
records."""
+ aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
+ type_ = "_test1-srvc-type._tcp.local."
+ name = "xxxyyy"
+ name2 = "xxxyyy2"
+
+ registration_name = f"{name}.{type_}"
+ registration_name2 = f"{name2}.{type_}"
+
+ calls = []
+
+ class MyListener(ServiceListener):
+ def add_service(self, zeroconf: Zeroconf, type: str, name: str) ->
None:
+ calls.append(("add", type, name))
+
+ def remove_service(self, zeroconf: Zeroconf, type: str, name: str) ->
None:
+ calls.append(("remove", type, name))
+
+ def update_service(self, zeroconf: Zeroconf, type: str, name: str) ->
None:
+ calls.append(("update", type, name))
+
+ listener = MyListener()
+
+ aiozc.zeroconf.add_service_listener(type_, listener)
+
+ desc = {'path': '/~paulsm/'}
+ info = ServiceInfo(
+ type_,
+ registration_name,
+ 80,
+ 0,
+ 0,
+ desc,
+ "ash-2.local.",
+ addresses=[socket.inet_aton("10.0.1.2")],
+ )
+ info2 = ServiceInfo(
+ type_,
+ registration_name2,
+ 81,
+ 0,
+ 0,
+ desc,
+ "ash-2.local.",
+ addresses=[socket.inet_aton("10.0.1.2")],
+ )
+ tasks = []
+ tasks.append(await aiozc.async_register_service(info))
+ tasks.append(await aiozc.async_register_service(info2))
+ await asyncio.gather(*tasks)
+
+ task = await aiozc.async_unregister_service(info)
+ await task
+ entries = aiozc.zeroconf.cache.async_entries_with_server("ash-2.local.")
+ assert len(entries) == 1
+ assert info2.dns_service() in entries
+ await aiozc.async_close()
+ assert calls == [
+ ('add', type_, registration_name),
+ ('add', type_, registration_name2),
+ ('remove', type_, registration_name),
+ ('remove', type_, registration_name2),
+ ]
+
+
[email protected]
+async def test_async_service_registration_same_server_same_ports() -> None:
+ """Test registering services with the same server with the exact same srv
record."""
+ aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
+ type_ = "_test1-srvc-type._tcp.local."
+ name = "xxxyyy"
+ name2 = "xxxyyy2"
+
+ registration_name = f"{name}.{type_}"
+ registration_name2 = f"{name2}.{type_}"
+
+ calls = []
+
+ class MyListener(ServiceListener):
+ def add_service(self, zeroconf: Zeroconf, type: str, name: str) ->
None:
+ calls.append(("add", type, name))
+
+ def remove_service(self, zeroconf: Zeroconf, type: str, name: str) ->
None:
+ calls.append(("remove", type, name))
+
+ def update_service(self, zeroconf: Zeroconf, type: str, name: str) ->
None:
+ calls.append(("update", type, name))
+
+ listener = MyListener()
+
+ aiozc.zeroconf.add_service_listener(type_, listener)
+
+ desc = {'path': '/~paulsm/'}
+ info = ServiceInfo(
+ type_,
+ registration_name,
+ 80,
+ 0,
+ 0,
+ desc,
+ "ash-2.local.",
+ addresses=[socket.inet_aton("10.0.1.2")],
+ )
+ info2 = ServiceInfo(
+ type_,
+ registration_name2,
+ 80,
+ 0,
+ 0,
+ desc,
+ "ash-2.local.",
+ addresses=[socket.inet_aton("10.0.1.2")],
+ )
+ tasks = []
+ tasks.append(await aiozc.async_register_service(info))
+ tasks.append(await aiozc.async_register_service(info2))
+ await asyncio.gather(*tasks)
+
+ task = await aiozc.async_unregister_service(info)
+ await task
+ entries = aiozc.zeroconf.cache.async_entries_with_server("ash-2.local.")
+ assert len(entries) == 1
+ assert info2.dns_service() in entries
+ await aiozc.async_close()
+ assert calls == [
+ ('add', type_, registration_name),
+ ('add', type_, registration_name2),
+ ('remove', type_, registration_name),
+ ('remove', type_, registration_name2),
+ ]
+
+
[email protected]
async def test_async_service_registration_name_conflict() -> None:
"""Test registering services throws on name conflict."""
aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/tests/test_dns.py
new/python-zeroconf-0.36.2/tests/test_dns.py
--- old/python-zeroconf-0.35.1/tests/test_dns.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/tests/test_dns.py 2021-08-30
17:04:19.000000000 +0200
@@ -211,6 +211,21 @@
assert len(record_set) == 1
+def test_dns_record_hashablity_does_not_consider_unique():
+ """Test DNSRecord are hashable and unique is ignored."""
+
+ # Verify the unique value is not considered in the hash
+ record1 = r.DNSAddress(
+ 'irrelevant', const._TYPE_A, const._CLASS_IN | const._CLASS_UNIQUE,
const._DNS_OTHER_TTL, b'same'
+ )
+ record2 = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN,
const._DNS_OTHER_TTL, b'same')
+
+ assert record1.class_ == record2.class_
+ assert record1.__hash__() == record2.__hash__()
+ record_set = {record1, record2}
+ assert len(record_set) == 1
+
+
def test_dns_address_record_hashablity():
"""Test DNSAddress are hashable."""
address1 = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 1,
b'a')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/tests/test_handlers.py
new/python-zeroconf-0.36.2/tests/test_handlers.py
--- old/python-zeroconf-0.35.1/tests/test_handlers.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/tests/test_handlers.py 2021-08-30
17:04:19.000000000 +0200
@@ -108,8 +108,9 @@
_process_outgoing_packet(construct_outgoing_multicast_answers(question_answers.mcast_aggregate))
# The additonals should all be suppresed since they are all in the
answers section
+ # There will be one NSEC additional to indicate the lack of AAAA record
#
- assert nbr_answers == 4 and nbr_additionals == 0 and nbr_authorities
== 0
+ assert nbr_answers == 4 and nbr_additionals == 1 and nbr_authorities
== 0
nbr_answers = nbr_additionals = nbr_authorities = 0
# unregister
@@ -143,7 +144,9 @@
[r.DNSIncoming(packet) for packet in query.packets()], False
)
_process_outgoing_packet(construct_outgoing_multicast_answers(question_answers.mcast_aggregate))
- assert nbr_answers == 4 and nbr_additionals == 0 and nbr_authorities
== 0
+
+ # There will be one NSEC additional to indicate the lack of AAAA record
+ assert nbr_answers == 4 and nbr_additionals == 1 and nbr_authorities
== 0
nbr_answers = nbr_additionals = nbr_authorities = 0
# unregister
@@ -271,7 +274,9 @@
has_txt = True
elif answer.type == const._TYPE_A:
has_a = True
- assert nbr_answers == 1 and nbr_additionals == 3
+ assert nbr_answers == 1 and nbr_additionals == 4
+ # There will be one NSEC additional to indicate the lack of AAAA record
+
assert has_srv and has_txt and has_a
# unregister
@@ -406,7 +411,7 @@
[r.DNSIncoming(packet) for packet in query.packets()], True
)
for answers in (question_answers.ucast, question_answers.mcast_aggregate):
- has_srv = has_txt = has_a = False
+ has_srv = has_txt = has_a = has_aaaa = has_nsec = False
nbr_additionals = 0
nbr_answers = len(answers)
additionals = set().union(*answers.values())
@@ -418,8 +423,14 @@
has_txt = True
elif answer.type == const._TYPE_A:
has_a = True
- assert nbr_answers == 1 and nbr_additionals == 3
- assert has_srv and has_txt and has_a
+ elif answer.type == const._TYPE_AAAA:
+ has_aaaa = True
+ elif answer.type == const._TYPE_NSEC:
+ has_nsec = True
+ # There will be one NSEC additional to indicate the lack of AAAA record
+ assert nbr_answers == 1 and nbr_additionals == 4
+ assert has_srv and has_txt and has_a and has_nsec
+ assert not has_aaaa
# unregister
zc.registry.async_remove(info)
@@ -497,7 +508,7 @@
zc.register_service(info)
def _validate_complete_response(answers):
- has_srv = has_txt = has_a = False
+ has_srv = has_txt = has_a = has_aaaa = has_nsec = False
nbr_answers = len(answers.keys())
additionals = set().union(*answers.values())
nbr_additionals = len(additionals)
@@ -509,8 +520,13 @@
has_txt = True
elif answer.type == const._TYPE_A:
has_a = True
- assert nbr_answers == 1 and nbr_additionals == 3
- assert has_srv and has_txt and has_a
+ elif answer.type == const._TYPE_AAAA:
+ has_aaaa = True
+ elif answer.type == const._TYPE_NSEC:
+ has_nsec = True
+ assert nbr_answers == 1 and nbr_additionals == 4
+ assert has_srv and has_txt and has_a and has_nsec
+ assert not has_aaaa
# With QU should respond to only unicast when the answer has been recently
multicast
query = r.DNSOutgoing(const._FLAGS_QR_QUERY)
@@ -635,6 +651,21 @@
assert not question_answers.mcast_aggregate
assert not question_answers.mcast_aggregate_last_second
+ # Test NSEC record returned when there is no AAAA record and we expectly
ask
+ generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
+ question = r.DNSQuestion(server_name, const._TYPE_AAAA, const._CLASS_IN)
+ generated.add_question(question)
+ for dns_address in info.dns_addresses():
+ generated.add_answer_at_time(dns_address, now)
+ packets = generated.packets()
+ question_answers = zc.query_handler.async_response([r.DNSIncoming(packet)
for packet in packets], False)
+ assert not question_answers.ucast
+ expected_nsec_record: r.DNSNsec = list(question_answers.mcast_now)[0]
+ assert const._TYPE_A not in expected_nsec_record.rdtypes
+ assert const._TYPE_AAAA in expected_nsec_record.rdtypes
+ assert not question_answers.mcast_aggregate
+ assert not question_answers.mcast_aggregate_last_second
+
# Test SRV supression
generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
question = r.DNSQuestion(registration_name, const._TYPE_SRV,
const._CLASS_IN)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/tests/test_protocol.py
new/python-zeroconf-0.36.2/tests/test_protocol.py
--- old/python-zeroconf-0.35.1/tests/test_protocol.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/tests/test_protocol.py 2021-08-30
17:04:19.000000000 +0200
@@ -54,6 +54,35 @@
generated.add_question(r.DNSQuestion("testname.local.",
const._TYPE_SRV, const._CLASS_IN))
r.DNSIncoming(generated.packets()[0])
+ def test_parse_own_packet_nsec(self):
+ answer = r.DNSNsec(
+ 'eufy HomeBase2-2464._hap._tcp.local.',
+ const._TYPE_NSEC,
+ const._CLASS_IN | const._CLASS_UNIQUE,
+ const._DNS_OTHER_TTL,
+ 'eufy HomeBase2-2464._hap._tcp.local.',
+ [const._TYPE_TXT, const._TYPE_SRV],
+ )
+
+ generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
+ generated.add_answer_at_time(answer, 0)
+ parsed = r.DNSIncoming(generated.packets()[0])
+ assert answer in parsed.answers
+
+ # Types > 255 should be ignored
+ answer_invalid_types = r.DNSNsec(
+ 'eufy HomeBase2-2464._hap._tcp.local.',
+ const._TYPE_NSEC,
+ const._CLASS_IN | const._CLASS_UNIQUE,
+ const._DNS_OTHER_TTL,
+ 'eufy HomeBase2-2464._hap._tcp.local.',
+ [const._TYPE_TXT, const._TYPE_SRV, 1000],
+ )
+ generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
+ generated.add_answer_at_time(answer_invalid_types, 0)
+ parsed = r.DNSIncoming(generated.packets()[0])
+ assert answer in parsed.answers
+
def test_parse_own_packet_response(self):
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
generated.add_answer_at_time(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/zeroconf/__init__.py
new/python-zeroconf-0.36.2/zeroconf/__init__.py
--- old/python-zeroconf-0.35.1/zeroconf/__init__.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/zeroconf/__init__.py 2021-08-30
17:04:19.000000000 +0200
@@ -80,7 +80,7 @@
__author__ = 'Paul Scott-Murphy, William McBrine'
__maintainer__ = 'Jakub Stasiak <[email protected]>'
-__version__ = '0.35.1'
+__version__ = '0.36.2'
__license__ = 'LGPL'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/zeroconf/_core.py
new/python-zeroconf-0.36.2/zeroconf/_core.py
--- old/python-zeroconf-0.35.1/zeroconf/_core.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/zeroconf/_core.py 2021-08-30
17:04:19.000000000 +0200
@@ -571,17 +571,28 @@
self.registry.async_update(info)
return asyncio.ensure_future(self._async_broadcast_service(info,
_REGISTER_TIME, None))
- async def _async_broadcast_service(self, info: ServiceInfo, interval: int,
ttl: Optional[int]) -> None:
+ async def _async_broadcast_service(
+ self,
+ info: ServiceInfo,
+ interval: int,
+ ttl: Optional[int],
+ broadcast_addresses: bool = True,
+ ) -> None:
"""Send a broadcasts to announce a service at intervals."""
for i in range(_REGISTER_BROADCASTS):
if i != 0:
await asyncio.sleep(millis_to_seconds(interval))
- self.async_send(self.generate_service_broadcast(info, ttl))
+ self.async_send(self.generate_service_broadcast(info, ttl,
broadcast_addresses))
- def generate_service_broadcast(self, info: ServiceInfo, ttl:
Optional[int]) -> DNSOutgoing:
+ def generate_service_broadcast(
+ self,
+ info: ServiceInfo,
+ ttl: Optional[int],
+ broadcast_addresses: bool = True,
+ ) -> DNSOutgoing:
"""Generate a broadcast to announce a service."""
out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
- self._add_broadcast_answer(out, info, ttl)
+ self._add_broadcast_answer(out, info, ttl, broadcast_addresses)
return out
def generate_service_query(self, info: ServiceInfo) -> DNSOutgoing: #
pylint: disable=no-self-use
@@ -600,7 +611,11 @@
return out
def _add_broadcast_answer( # pylint: disable=no-self-use
- self, out: DNSOutgoing, info: ServiceInfo, override_ttl: Optional[int]
+ self,
+ out: DNSOutgoing,
+ info: ServiceInfo,
+ override_ttl: Optional[int],
+ broadcast_addresses: bool = True,
) -> None:
"""Add answers to broadcast a service."""
now = current_time_millis()
@@ -609,8 +624,9 @@
out.add_answer_at_time(info.dns_pointer(override_ttl=other_ttl,
created=now), 0)
out.add_answer_at_time(info.dns_service(override_ttl=host_ttl,
created=now), 0)
out.add_answer_at_time(info.dns_text(override_ttl=other_ttl,
created=now), 0)
- for dns_address in info.dns_addresses(override_ttl=host_ttl,
created=now):
- out.add_answer_at_time(dns_address, 0)
+ if broadcast_addresses:
+ for dns_address in info.dns_addresses(override_ttl=host_ttl,
created=now):
+ out.add_answer_at_time(dns_address, 0)
def unregister_service(self, info: ServiceInfo) -> None:
"""Unregister a service."""
@@ -622,7 +638,14 @@
async def async_unregister_service(self, info: ServiceInfo) -> Awaitable:
"""Unregister a service."""
self.registry.async_remove(info)
- return asyncio.ensure_future(self._async_broadcast_service(info,
_UNREGISTER_TIME, 0))
+ # If another server uses the same addresses, we do not want to send
+ # goodbye packets for the address records
+
+ entries = self.registry.async_get_infos_server(info.server)
+ broadcast_addresses = not bool(entries)
+ return asyncio.ensure_future(
+ self._async_broadcast_service(info, _UNREGISTER_TIME, 0,
broadcast_addresses)
+ )
def generate_unregister_all_services(self) -> Optional[DNSOutgoing]:
"""Generate a DNSOutgoing goodbye for all services and remove them
from the registry."""
@@ -834,6 +857,11 @@
out,
packet,
)
+ # Get flowinfo and scopeid for the IPV6 socket to create a complete
IPv6
+ # address tuple:
https://docs.python.org/3.6/library/socket.html#socket-families
+ if s.family == socket.AF_INET6 and not v6_flow_scope:
+ _, _, sock_flowinfo, sock_scopeid = s.getsockname()
+ v6_flow_scope = (sock_flowinfo, sock_scopeid)
transport.sendto(packet, (real_addr, port or _MDNS_PORT,
*v6_flow_scope))
def _close(self) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/zeroconf/_dns.py
new/python-zeroconf-0.36.2/zeroconf/_dns.py
--- old/python-zeroconf-0.35.1/zeroconf/_dns.py 2021-08-15 21:14:10.000000000
+0200
+++ new/python-zeroconf-0.36.2/zeroconf/_dns.py 2021-08-30 17:04:19.000000000
+0200
@@ -115,7 +115,7 @@
def __init__(self, name: str, type_: int, class_: int) -> None:
super().__init__(name, type_, class_)
- self._hash = hash((self.key, type_, class_))
+ self._hash = hash((self.key, type_, self.class_))
def answered_by(self, rec: 'DNSRecord') -> bool:
"""Returns true if the question is answered by the record"""
@@ -247,7 +247,7 @@
super().__init__(name, type_, class_, ttl, created)
self.address = address
self.scope_id = scope_id
- self._hash = hash((self.key, type_, class_, address, scope_id))
+ self._hash = hash((self.key, type_, self.class_, address, scope_id))
def write(self, out: 'DNSOutgoing') -> None:
"""Used in constructing an outgoing packet"""
@@ -290,7 +290,7 @@
super().__init__(name, type_, class_, ttl, created)
self.cpu = cpu
self.os = os
- self._hash = hash((self.key, type_, class_, cpu, os))
+ self._hash = hash((self.key, type_, self.class_, cpu, os))
def write(self, out: 'DNSOutgoing') -> None:
"""Used in constructing an outgoing packet"""
@@ -326,7 +326,7 @@
) -> None:
super().__init__(name, type_, class_, ttl, created)
self.alias = alias
- self._hash = hash((self.key, type_, class_, alias))
+ self._hash = hash((self.key, type_, self.class_, alias))
@property
def max_size_compressed(self) -> int:
@@ -367,7 +367,7 @@
assert isinstance(text, (bytes, type(None)))
super().__init__(name, type_, class_, ttl, created)
self.text = text
- self._hash = hash((self.key, type_, class_, text))
+ self._hash = hash((self.key, type_, self.class_, text))
def write(self, out: 'DNSOutgoing') -> None:
"""Used in constructing an outgoing packet"""
@@ -411,7 +411,7 @@
self.weight = weight
self.port = port
self.server = server
- self._hash = hash((self.key, type_, class_, priority, weight, port,
server))
+ self._hash = hash((self.key, type_, self.class_, priority, weight,
port, server))
def write(self, out: 'DNSOutgoing') -> None:
"""Used in constructing an outgoing packet"""
@@ -458,8 +458,24 @@
) -> None:
super().__init__(name, type_, class_, ttl, created)
self.next_name = next_name
- self.rdtypes = rdtypes
- self._hash = hash((self.key, type_, class_, next_name, *self.rdtypes))
+ self.rdtypes = sorted(rdtypes)
+ self._hash = hash((self.key, type_, self.class_, next_name,
*self.rdtypes))
+
+ def write(self, out: 'DNSOutgoing') -> None:
+ """Used in constructing an outgoing packet."""
+ bitmap = bytearray(b'\0' * 32)
+ for rdtype in self.rdtypes:
+ if rdtype > 255: # mDNS only supports window 0
+ continue
+ offset = rdtype % 256
+ byte = offset // 8
+ total_octets = byte + 1
+ bitmap[byte] |= 0x80 >> (offset % 8)
+ out_bytes = bytes(bitmap[0:total_octets])
+ out.write_name(self.next_name)
+ out.write_short(0)
+ out.write_short(len(out_bytes))
+ out.write_string(out_bytes)
def __eq__(self, other: Any) -> bool:
"""Tests equality on cpu and os"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.35.1/zeroconf/_handlers.py
new/python-zeroconf-0.36.2/zeroconf/_handlers.py
--- old/python-zeroconf-0.35.1/zeroconf/_handlers.py 2021-08-15
21:14:10.000000000 +0200
+++ new/python-zeroconf-0.36.2/zeroconf/_handlers.py 2021-08-30
17:04:19.000000000 +0200
@@ -26,15 +26,17 @@
from typing import Dict, Iterable, List, NamedTuple, Optional, Set,
TYPE_CHECKING, Tuple, Union, cast
from ._cache import DNSCache, _UniqueRecordsType
-from ._dns import DNSAddress, DNSPointer, DNSQuestion, DNSRRSet, DNSRecord
+from ._dns import DNSAddress, DNSNsec, DNSPointer, DNSQuestion, DNSRRSet,
DNSRecord
from ._history import QuestionHistory
from ._logger import log
from ._protocol import DNSIncoming, DNSOutgoing
+from ._services.info import ServiceInfo
from ._services.registry import ServiceRegistry
from ._updates import RecordUpdate, RecordUpdateListener
from ._utils.time import current_time_millis, millis_to_seconds
from .const import (
_CLASS_IN,
+ _CLASS_UNIQUE,
_DNS_OTHER_TTL,
_DNS_PTR_MIN_TTL,
_FLAGS_AA,
@@ -44,6 +46,7 @@
_TYPE_A,
_TYPE_AAAA,
_TYPE_ANY,
+ _TYPE_NSEC,
_TYPE_PTR,
_TYPE_SRV,
_TYPE_TXT,
@@ -56,7 +59,8 @@
_AnswerWithAdditionalsType = Dict[DNSRecord, Set[DNSRecord]]
_MULTICAST_DELAY_RANDOM_INTERVAL = (20, 120)
-_RESPOND_IMMEDIATE_TYPES = {_TYPE_SRV, _TYPE_A, _TYPE_AAAA}
+_ADDRESS_RECORD_TYPES = {_TYPE_A, _TYPE_AAAA}
+_RESPOND_IMMEDIATE_TYPES = {_TYPE_NSEC, _TYPE_SRV, *_ADDRESS_RECORD_TYPES}
class QuestionAnswers(NamedTuple):
@@ -78,6 +82,15 @@
return msg.num_authorities > 0
+def construct_nsec_record(name: str, types: List[int], now: float) -> DNSNsec:
+ """Construct an NSEC record for name and a list of dns types.
+
+ This function should only be used for SRV/A/AAAA records
+ which have a TTL of _DNS_OTHER_TTL
+ """
+ return DNSNsec(name, _TYPE_NSEC, _CLASS_IN | _CLASS_UNIQUE,
_DNS_OTHER_TTL, name, types, created=now)
+
+
def construct_outgoing_multicast_answers(answers: _AnswerWithAdditionalsType)
-> DNSOutgoing:
"""Add answers and additionals to a DNSOutgoing."""
out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, multicast=True)
@@ -244,12 +257,23 @@
# Add recommended additional answers according to
# https://tools.ietf.org/html/rfc6763#section-12.1.
dns_pointer = service.dns_pointer(created=now)
- if not known_answers.suppresses(dns_pointer):
- answer_set[dns_pointer] = {
- service.dns_service(created=now),
- service.dns_text(created=now),
- *service.dns_addresses(created=now),
- }
+ if known_answers.suppresses(dns_pointer):
+ continue
+ additionals: Set[DNSRecord] = {service.dns_service(created=now),
service.dns_text(created=now)}
+ additionals |= self._get_address_and_nsec_records(service, now)
+ answer_set[dns_pointer] = additionals
+
+ def _get_address_and_nsec_records(self, service: ServiceInfo, now: float)
-> Set[DNSRecord]:
+ """Build a set of address records and NSEC records for non-present
record types."""
+ seen_types: Set[int] = set()
+ records: Set[DNSRecord] = set()
+ for dns_address in service.dns_addresses(created=now):
+ seen_types.add(dns_address.type)
+ records.add(dns_address)
+ missing_types: Set[int] = _ADDRESS_RECORD_TYPES - seen_types
+ if missing_types:
+ records.add(construct_nsec_record(service.server,
list(missing_types), now))
+ return records
def _add_address_answers(
self,
@@ -263,13 +287,21 @@
for service in self.registry.async_get_infos_server(name):
answers: List[DNSAddress] = []
additionals: Set[DNSRecord] = set()
+ seen_types: Set[int] = set()
for dns_address in service.dns_addresses(created=now):
+ seen_types.add(dns_address.type)
if dns_address.type != type_:
additionals.add(dns_address)
elif not known_answers.suppresses(dns_address):
answers.append(dns_address)
- for answer in answers:
- answer_set[answer] = additionals
+ missing_types: Set[int] = _ADDRESS_RECORD_TYPES - seen_types
+ if answers:
+ if missing_types:
+ additionals.add(construct_nsec_record(service.server,
list(missing_types), now))
+ for answer in answers:
+ answer_set[answer] = additionals
+ elif type_ in missing_types:
+ answer_set[construct_nsec_record(service.server,
list(missing_types), now)] = set()
def _answer_question(
self,
@@ -299,7 +331,7 @@
# https://tools.ietf.org/html/rfc6763#section-12.2.
dns_service = service.dns_service(created=now)
if not known_answers.suppresses(dns_service):
- answer_set[dns_service] =
set(service.dns_addresses(created=now))
+ answer_set[dns_service] =
self._get_address_and_nsec_records(service, now)
if type_ in (_TYPE_TXT, _TYPE_ANY):
dns_text = service.dns_text(created=now)
if not known_answers.suppresses(dns_text):