zeroconf 0.151.3__tar.gz → 0.151.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zeroconf-0.151.3 → zeroconf-0.151.6}/CHANGELOG.md +27 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/PKG-INFO +2 -1
- {zeroconf-0.151.3 → zeroconf-0.151.6}/pyproject.toml +4 -4
- {zeroconf-0.151.3 → zeroconf-0.151.6}/setup.py +1 -1
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/__init__.py +1 -1
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_cache.py +2 -2
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/query_handler.py +3 -2
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_listener.py +1 -1
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/info.pxd +3 -1
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/info.py +8 -8
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/net.py +11 -3
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/const.py +4 -2
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_listener.py +20 -1
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/utils/test_net.py +29 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/COPYING +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/README.rst +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/build_ext.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/docs/Makefile +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/docs/_ext/zeroconfautodocfix.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/docs/api.rst +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/docs/conf.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/docs/index.rst +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_cache.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_core.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_dns.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_dns.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_engine.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_exceptions.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/answers.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/answers.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/multicast_outgoing_queue.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/multicast_outgoing_queue.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/query_handler.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/record_manager.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_handlers/record_manager.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_history.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_history.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_listener.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_logger.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_protocol/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_protocol/incoming.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_protocol/incoming.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_protocol/outgoing.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_protocol/outgoing.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_record_update.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_record_update.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/__init__.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/browser.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/browser.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/registry.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/registry.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_services/types.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_transport.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_updates.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_updates.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/asyncio.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/ipaddress.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/ipaddress.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/name.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/time.pxd +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/_utils/time.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/asyncio.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/src/zeroconf/py.typed +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/helpers.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_cache.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_cache_bound.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_cache_mark_expire.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_incoming.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_ipaddress.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_listener_dedup.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_mark_seen.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_outgoing.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_registry.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_send.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/benchmarks/test_txt_properties.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/conftest.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/services/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/services/test_browser.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/services/test_info.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/services/test_registry.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/services/test_types.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_asyncio.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_cache.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_circular_imports.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_core.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_dns.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_engine.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_exceptions.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_fuzz_incoming.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_handlers.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_history.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_init.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_interface_update.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_logger.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_protocol.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_services.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/test_updates.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/utils/__init__.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/utils/test_asyncio.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/utils/test_ipaddress.py +0 -0
- {zeroconf-0.151.3 → zeroconf-0.151.6}/tests/utils/test_name.py +0 -0
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v0.151.6 (2026-10-31)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Shorten the duplicate packet window to 800 ms
|
|
10
|
+
([#1930](https://github.com/python-zeroconf/python-zeroconf/pull/1930),
|
|
11
|
+
[`c245c2f`](https://github.com/python-zeroconf/python-zeroconf/commit/c245c2fab48ebedd145895b9fa5ee36f86715159))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## v0.151.5 (2026-09-28)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- Build only the sdist in the release job
|
|
19
|
+
([#1921](https://github.com/python-zeroconf/python-zeroconf/pull/1921),
|
|
20
|
+
[`1d939f4`](https://github.com/python-zeroconf/python-zeroconf/commit/1d939f471f045a2e4a5d44cfe35cf4f61e55c5ba))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## v0.151.4 (2026-09-28)
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
- Skip adapter scan for interface lists with no IPv6 entries
|
|
28
|
+
([#1919](https://github.com/python-zeroconf/python-zeroconf/pull/1919),
|
|
29
|
+
[`47ec4ba`](https://github.com/python-zeroconf/python-zeroconf/commit/47ec4bab70d548503b937ba357f160eebfa2af1f))
|
|
30
|
+
|
|
31
|
+
|
|
5
32
|
## v0.151.3 (2026-08-30)
|
|
6
33
|
|
|
7
34
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zeroconf
|
|
3
|
-
Version: 0.151.
|
|
3
|
+
Version: 0.151.6
|
|
4
4
|
Summary: A pure python implementation of multicast DNS service discovery
|
|
5
5
|
License-Expression: LGPL-2.1-or-later
|
|
6
6
|
License-File: COPYING
|
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
23
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
24
25
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
25
26
|
Classifier: Topic :: Software Development :: Libraries
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zeroconf"
|
|
7
|
-
version = "0.151.
|
|
7
|
+
version = "0.151.6"
|
|
8
8
|
license = "LGPL-2.1-or-later"
|
|
9
9
|
description = "A pure python implementation of multicast DNS service discovery"
|
|
10
10
|
readme = "README.rst"
|
|
@@ -56,7 +56,7 @@ version_toml = ["pyproject.toml:project.version"]
|
|
|
56
56
|
version_variables = [
|
|
57
57
|
"src/zeroconf/__init__.py:__version__"
|
|
58
58
|
]
|
|
59
|
-
build_command = "pip install poetry && poetry build"
|
|
59
|
+
build_command = "pip install poetry && poetry build --format sdist"
|
|
60
60
|
tag_format = "{version}"
|
|
61
61
|
allow_zero_version = true
|
|
62
62
|
|
|
@@ -91,8 +91,8 @@ cython = "^3.3.0"
|
|
|
91
91
|
setuptools = ">=84.0.0,<85.0.0"
|
|
92
92
|
pytest-timeout = "^2.1.0"
|
|
93
93
|
pytest-codspeed = ">=5.0.3,<6.0"
|
|
94
|
-
blockbuster = ">=1.5.
|
|
95
|
-
hypothesis = {version = "^6.
|
|
94
|
+
blockbuster = ">=1.5.29,<2.0.0"
|
|
95
|
+
hypothesis = {version = "^6.168.5", markers = "implementation_name == 'cpython'"}
|
|
96
96
|
|
|
97
97
|
[tool.poetry.group.docs.dependencies]
|
|
98
98
|
sphinx = "^7.4.7 || ^8.1.3"
|
|
@@ -19,7 +19,7 @@ install_requires = \
|
|
|
19
19
|
|
|
20
20
|
setup_kwargs = {
|
|
21
21
|
'name': 'zeroconf',
|
|
22
|
-
'version': '0.151.
|
|
22
|
+
'version': '0.151.6',
|
|
23
23
|
'description': 'A pure python implementation of multicast DNS service discovery',
|
|
24
24
|
'long_description': 'python-zeroconf\n===============\n\n.. image:: https://github.com/python-zeroconf/python-zeroconf/workflows/CI/badge.svg\n :target: https://github.com/python-zeroconf/python-zeroconf?query=workflow%3ACI+branch%3Amaster\n\n.. image:: https://img.shields.io/pypi/v/zeroconf.svg\n :target: https://pypi.python.org/pypi/zeroconf\n\n.. image:: https://codecov.io/gh/python-zeroconf/python-zeroconf/branch/master/graph/badge.svg\n :target: https://codecov.io/gh/python-zeroconf/python-zeroconf\n\n.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json\n :target: https://codspeed.io/python-zeroconf/python-zeroconf\n :alt: Codspeed.io status for python-zeroconf\n\n.. image:: https://readthedocs.org/projects/python-zeroconf/badge/?version=latest\n :target: https://python-zeroconf.readthedocs.io/en/latest/?badge=latest\n :alt: Documentation Status\n\n`Documentation <https://python-zeroconf.readthedocs.io/en/latest/>`_.\n\nThis project is a fork of pyzeroconf, originally by Paul Scott-Murphy\n(https://github.com/paulsm/pyzeroconf), modified by William McBrine\n(https://github.com/wmcbrine/pyzeroconf).\n\nCompatible with:\n\n* Bonjour\n* Avahi\n\nCompared to some other Zeroconf/Bonjour/Avahi Python packages, python-zeroconf:\n\n* isn\'t tied to Bonjour or Avahi\n* doesn\'t use D-Bus\n* doesn\'t force you to use particular event loop or Twisted (asyncio is used under the hood but not required)\n* is pip-installable\n* has PyPI distribution\n* has an optional cython extension for performance (pure python is supported as well)\n\nPython compatibility\n--------------------\n\n* CPython 3.10+\n* PyPy 3.10+\n\nVersioning\n----------\n\nThis project uses semantic versioning.\n\nStatus\n------\n\nThis project is actively maintained.\n\nTraffic Reduction\n-----------------\n\nBefore version 0.32, most traffic reduction techniques described in https://datatracker.ietf.org/doc/html/rfc6762#section-7\nwhere not implemented which could lead to excessive network traffic. It is highly recommended that version 0.32 or later\nis used if this is a concern.\n\nIPv6 support\n------------\n\nIPv6 support is relatively new and currently limited, specifically:\n\n* `InterfaceChoice.All` is an alias for `InterfaceChoice.Default` on non-POSIX\n systems.\n* Dual-stack IPv6 sockets are used, which may not be supported everywhere (some\n BSD variants do not have them).\n* Listening on localhost (`::1`) does not work. Help with understanding why is\n appreciated.\n\nHow to get python-zeroconf?\n===========================\n\n* PyPI page https://pypi.org/project/zeroconf/\n* GitHub project https://github.com/python-zeroconf/python-zeroconf\n\nThe easiest way to install python-zeroconf is using pip::\n\n pip install zeroconf\n\n\n\nHow do I use it?\n================\n\nHere\'s how to discover services:\n\n.. code-block:: python\n\n from zeroconf import ServiceBrowser, ServiceListener, Zeroconf\n\n\n class EventLogger(ServiceListener):\n def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n info = zc.get_service_info(type_, name)\n print(f"discovered {name}: {info}")\n\n def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n print(f"lost {name}")\n\n def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n print(f"refreshed {name}")\n\n\n with Zeroconf() as zc:\n ServiceBrowser(zc, "_http._tcp.local.", EventLogger())\n input("browsing, press enter to stop\\n")\n\n.. note::\n\n Discovery and service registration use *all* available network interfaces by default.\n If you want to customize that you need to specify ``interfaces`` argument when\n constructing ``Zeroconf`` object (see the code for details).\n\nTo see which service types are present on the network, try:\n\n.. code-block:: python\n\n from zeroconf import ZeroconfServiceTypes\n print(\'\\n\'.join(ZeroconfServiceTypes.find()))\n\nSee examples directory for more.\n\nChangelog\n=========\n\n`Changelog <CHANGELOG.md>`_\n\nLicense\n=======\n\nGNU Lesser General Public License v2.1 or later (LGPL-2.1-or-later).\n\nThe full text of LGPL 2.1 is included in the `COPYING <COPYING>`_ file.\nYou may, at your option, use this library under the terms of any later\nversion of the LGPL published by the Free Software Foundation. The\ncanonical SPDX identifier for this project is ``LGPL-2.1-or-later``, as\ndeclared in ``pyproject.toml``.\n',
|
|
25
25
|
'author': 'The python-zeroconf authors',
|
|
@@ -73,7 +73,7 @@ from ._utils.time import ( # noqa # import needed for backwards compat
|
|
|
73
73
|
|
|
74
74
|
__author__ = "The python-zeroconf authors" # full history in COPYING and git
|
|
75
75
|
__maintainer__ = "J. Nick Koston <nick@koston.org>"
|
|
76
|
-
__version__ = "0.151.
|
|
76
|
+
__version__ = "0.151.6"
|
|
77
77
|
__license__ = "LGPL"
|
|
78
78
|
|
|
79
79
|
|
|
@@ -77,7 +77,7 @@ class DNSCache:
|
|
|
77
77
|
return new
|
|
78
78
|
|
|
79
79
|
def async_all_by_details(self, name: _str, type_: _int, class_: _int) -> list[DNSRecord]:
|
|
80
|
-
"""
|
|
80
|
+
"""Return every cached record carrying this name, type and class.
|
|
81
81
|
|
|
82
82
|
This function is not thread-safe and must be called from
|
|
83
83
|
the event loop.
|
|
@@ -206,7 +206,7 @@ class DNSCache:
|
|
|
206
206
|
return [entry for entry in list(records.values()) if type_ == entry.type and class_ == entry.class_]
|
|
207
207
|
|
|
208
208
|
def get_by_details(self, name: str, type_: _int, class_: _int) -> DNSRecord | None:
|
|
209
|
-
"""
|
|
209
|
+
"""Return the first cached record carrying this name, type and class, or None.
|
|
210
210
|
|
|
211
211
|
Calling this function is not recommended as it will only
|
|
212
212
|
return one record even if there are multiple entries.
|
|
@@ -247,8 +247,9 @@ class QueryHandler:
|
|
|
247
247
|
continue
|
|
248
248
|
if ucast_source:
|
|
249
249
|
query_res.add_ucast_question_response(answer_set)
|
|
250
|
-
#
|
|
251
|
-
# source
|
|
250
|
+
# The answer also goes out by multicast even for a unicast
|
|
251
|
+
# source; a copy multicast within the last second is folded
|
|
252
|
+
# into the aggregated response instead (RFC 6762 section 14)
|
|
252
253
|
query_res.add_mcast_question_response(answer_set)
|
|
253
254
|
|
|
254
255
|
return query_res.answers()
|
|
@@ -251,7 +251,7 @@ class AsyncListener:
|
|
|
251
251
|
# _DUPLICATE_PACKET_SUPPRESSION_INTERVAL` so a cache miss never
|
|
252
252
|
# triggers the branch even when `now` is small (time.monotonic
|
|
253
253
|
# is allowed to start near zero, leaving `now - INTERVAL`
|
|
254
|
-
# negative for the first
|
|
254
|
+
# negative for the first interval after boot). Only non-QU payloads
|
|
255
255
|
# are cached, so any hit here is safe to suppress without re-
|
|
256
256
|
# checking has_qu_question.
|
|
257
257
|
recent_time = self._recent_packets.get(data, -1e30)
|
|
@@ -1044,26 +1044,26 @@ class ServiceInfo(RecordUpdateListener):
|
|
|
1044
1044
|
def _unpack_text_into_properties(self) -> None:
|
|
1045
1045
|
"""Unpacks the text field into properties"""
|
|
1046
1046
|
text = self.text
|
|
1047
|
-
|
|
1048
|
-
if
|
|
1047
|
+
total = len(text)
|
|
1048
|
+
if total == 0:
|
|
1049
1049
|
# Properties should be set atomically
|
|
1050
1050
|
# in case another thread is reading them
|
|
1051
1051
|
self._properties = {}
|
|
1052
1052
|
return
|
|
1053
1053
|
|
|
1054
|
-
index = 0
|
|
1055
1054
|
properties: dict[bytes, bytes | None] = {}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1055
|
+
pos = 0
|
|
1056
|
+
while pos < total:
|
|
1057
|
+
length = text[pos]
|
|
1058
|
+
start = pos + 1
|
|
1059
|
+
key_value = text[start : start + length]
|
|
1060
|
+
pos = start + length
|
|
1060
1061
|
key, sep, value = key_value.partition(b"=")
|
|
1061
1062
|
if key not in properties:
|
|
1062
1063
|
# RFC 6763 section 6.4 distinguishes a key with no '=' (a
|
|
1063
1064
|
# boolean attribute: present, no value) from `key=` (present
|
|
1064
1065
|
# with an empty value), so test the separator, not the value.
|
|
1065
1066
|
properties[key] = value if sep else None
|
|
1066
|
-
index += length
|
|
1067
1067
|
|
|
1068
1068
|
self._properties = properties
|
|
1069
1069
|
|
|
@@ -126,13 +126,21 @@ def ip6_addresses_to_indexes(
|
|
|
126
126
|
:param interfaces: List of IP addresses and indexes.
|
|
127
127
|
:returns: List of indexes.
|
|
128
128
|
"""
|
|
129
|
-
result = []
|
|
129
|
+
result: list[tuple[tuple[str, int, int], int]] = []
|
|
130
|
+
needs_scan = [
|
|
131
|
+
iface
|
|
132
|
+
for iface in interfaces
|
|
133
|
+
if isinstance(iface, int) or (isinstance(iface, str) and ipaddress.ip_address(iface).version == 6)
|
|
134
|
+
]
|
|
135
|
+
if not needs_scan:
|
|
136
|
+
return result
|
|
137
|
+
|
|
130
138
|
adapters = ifaddr.get_adapters()
|
|
131
139
|
|
|
132
|
-
for iface in
|
|
140
|
+
for iface in needs_scan:
|
|
133
141
|
if isinstance(iface, int):
|
|
134
142
|
result.append((interface_index_to_ip6_address(adapters, iface), iface)) # type: ignore[arg-type]
|
|
135
|
-
|
|
143
|
+
else:
|
|
136
144
|
result.append(ip6_to_address_and_index(adapters, iface)) # type: ignore[arg-type]
|
|
137
145
|
|
|
138
146
|
return result
|
|
@@ -15,14 +15,16 @@ _CHECK_TIME = 500 # ms
|
|
|
15
15
|
_LISTENER_TIME = 200 # ms
|
|
16
16
|
_REGISTER_TIME = 225 # ms
|
|
17
17
|
_UNREGISTER_TIME = 125 # ms
|
|
18
|
-
|
|
18
|
+
# Below the ~1s spacing of repeated announcements (RFC 6762 §8.3), which some
|
|
19
|
+
# responders jitter to a little under a second.
|
|
20
|
+
_DUPLICATE_PACKET_SUPPRESSION_INTERVAL = 800 # ms
|
|
19
21
|
# Per-listener bounded recency window. 16 is large enough to defeat
|
|
20
22
|
# the alternating-payload bypass (RFC 6762 §6.2, issue #1724 — even a
|
|
21
23
|
# rotation of a dozen distinct payloads still dedups), and small
|
|
22
24
|
# enough that the dict bookkeeping per miss stays cheap under a
|
|
23
25
|
# hostile flood.
|
|
24
26
|
_RECENT_PACKETS_MAX = 16
|
|
25
|
-
_DUPLICATE_QUESTION_INTERVAL = 999 # ms
|
|
27
|
+
_DUPLICATE_QUESTION_INTERVAL = 999 # ms
|
|
26
28
|
_CACHE_CLEANUP_INTERVAL = 10 # s
|
|
27
29
|
_LOADED_SYSTEM_TIMEOUT = 10 # s
|
|
28
30
|
_STARTUP_TIMEOUT = 9 # s must be lower than _LOADED_SYSTEM_TIMEOUT
|
|
@@ -389,7 +389,7 @@ def test_recent_packets_miss_with_small_now_is_not_suppressed(zc: Zeroconf) -> N
|
|
|
389
389
|
"""A cache miss must not trigger suppression when `now` is below the suppression interval."""
|
|
390
390
|
# time.monotonic() can start near zero on freshly booted systems, so
|
|
391
391
|
# `now - _DUPLICATE_PACKET_SUPPRESSION_INTERVAL` is negative for the
|
|
392
|
-
# first
|
|
392
|
+
# first interval of process lifetime. A 0.0 default on the recency
|
|
393
393
|
# dict would let any negative `now - INTERVAL` satisfy the compare
|
|
394
394
|
# and suppress legitimate traffic.
|
|
395
395
|
zc.registry.async_add(
|
|
@@ -425,3 +425,22 @@ def test_recent_packets_miss_with_small_now_is_not_suppressed(zc: Zeroconf) -> N
|
|
|
425
425
|
with patch.object(listener, "handle_query_or_defer") as _handle_query_or_defer:
|
|
426
426
|
listener._process_datagram_at_time(False, len(packet), 0.0, packet, addrs)
|
|
427
427
|
_handle_query_or_defer.assert_called_once()
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def test_repeated_announcement_under_one_second_is_processed(zc: Zeroconf) -> None:
|
|
431
|
+
"""An announcement repeated a little under a second later is not dropped as a duplicate."""
|
|
432
|
+
listener = _listener.AsyncListener(zc)
|
|
433
|
+
listener.transport = MagicMock()
|
|
434
|
+
response = r.DNSOutgoing(const._FLAGS_QR_RESPONSE | const._FLAGS_AA)
|
|
435
|
+
response.add_answer_at_time(
|
|
436
|
+
r.DNSAddress("device.local.", const._TYPE_A, const._CLASS_IN, 120, b"\x7f\x00\x00\x01"), 0
|
|
437
|
+
)
|
|
438
|
+
packet = response.packets()[0]
|
|
439
|
+
addrs = ("1.2.3.4", 5353)
|
|
440
|
+
|
|
441
|
+
now = current_time_millis()
|
|
442
|
+
listener._process_datagram_at_time(False, len(packet), now, packet, addrs)
|
|
443
|
+
listener._process_datagram_at_time(False, len(packet), now + 5, packet, addrs)
|
|
444
|
+
assert listener.last_time == now
|
|
445
|
+
listener._process_datagram_at_time(False, len(packet), now + 926, packet, addrs)
|
|
446
|
+
assert listener.last_time == now + 926
|
|
@@ -118,6 +118,35 @@ def test_ip6_addresses_to_indexes():
|
|
|
118
118
|
assert netutils.ip6_addresses_to_indexes(interfaces_2) == [(("2001:db8::", 1, 1), 1)]
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
@pytest.mark.parametrize("interfaces", [[], ["192.168.1.5"], ["192.168.1.5", "169.254.3.2"]])
|
|
122
|
+
def test_ip6_addresses_to_indexes_skips_scan_without_ipv6(interfaces: list[str]) -> None:
|
|
123
|
+
"""Test the adapters are not scanned when there are no IPv6 entries."""
|
|
124
|
+
with patch("zeroconf._utils.net.ifaddr.get_adapters") as mock_get_adapters:
|
|
125
|
+
assert netutils.ip6_addresses_to_indexes(interfaces) == []
|
|
126
|
+
mock_get_adapters.assert_not_called()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_ip6_addresses_to_indexes_scans_once() -> None:
|
|
130
|
+
"""Test the adapters are scanned once for a mixed list."""
|
|
131
|
+
with patch(
|
|
132
|
+
"zeroconf._utils.net.ifaddr.get_adapters",
|
|
133
|
+
return_value=_generate_mock_adapters(),
|
|
134
|
+
) as mock_get_adapters:
|
|
135
|
+
assert netutils.ip6_addresses_to_indexes(["192.168.1.5", "2001:db8::", 1, "fd00:db8::"]) == [
|
|
136
|
+
(("2001:db8::", 1, 1), 1),
|
|
137
|
+
(("2001:db8::", 1, 1), 1),
|
|
138
|
+
(("fd00:db8::", 1, 1), 1),
|
|
139
|
+
]
|
|
140
|
+
mock_get_adapters.assert_called_once()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_normalize_interface_choice_ipv4_list_skips_scan() -> None:
|
|
144
|
+
"""Test an IPv4 only list is normalized without scanning the adapters."""
|
|
145
|
+
with patch("zeroconf._utils.net.ifaddr.get_adapters") as mock_get_adapters:
|
|
146
|
+
assert netutils.normalize_interface_choice(["192.168.1.5"]) == ["192.168.1.5"]
|
|
147
|
+
mock_get_adapters.assert_not_called()
|
|
148
|
+
|
|
149
|
+
|
|
121
150
|
def test_normalize_interface_choice_errors():
|
|
122
151
|
"""Test we generate exception on invalid input."""
|
|
123
152
|
with (
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|