zeroconf 0.149.7__tar.gz → 0.149.8__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.
Potentially problematic release.
This version of zeroconf might be problematic. Click here for more details.
- {zeroconf-0.149.7 → zeroconf-0.149.8}/CHANGELOG.md +23 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/PKG-INFO +1 -1
- {zeroconf-0.149.7 → zeroconf-0.149.8}/pyproject.toml +4 -4
- {zeroconf-0.149.7 → zeroconf-0.149.8}/setup.py +1 -1
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/__init__.py +1 -1
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_protocol/incoming.pxd +1 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_protocol/incoming.py +14 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/__init__.py +14 -1
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/services/test_types.py +3 -3
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_handlers.py +6 -6
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_protocol.py +79 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/COPYING +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/README.rst +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/build_ext.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/docs/Makefile +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/docs/_ext/zeroconfautodocfix.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/docs/api.rst +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/docs/conf.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/docs/index.rst +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_cache.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_cache.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_core.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_dns.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_dns.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_engine.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_exceptions.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/__init__.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/answers.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/answers.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/multicast_outgoing_queue.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/multicast_outgoing_queue.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/query_handler.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/query_handler.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/record_manager.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_handlers/record_manager.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_history.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_history.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_listener.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_listener.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_logger.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_protocol/__init__.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_protocol/outgoing.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_protocol/outgoing.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_record_update.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_record_update.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/__init__.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/__init__.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/browser.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/browser.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/info.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/info.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/registry.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/registry.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_services/types.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_transport.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_updates.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_updates.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/__init__.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/asyncio.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/ipaddress.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/ipaddress.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/name.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/net.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/time.pxd +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/_utils/time.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/asyncio.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/const.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/src/zeroconf/py.typed +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/__init__.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/helpers.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/test_cache.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/test_cache_bound.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/test_incoming.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/test_mark_seen.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/test_outgoing.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/test_send.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/benchmarks/test_txt_properties.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/conftest.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/services/__init__.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/services/test_browser.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/services/test_info.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/services/test_registry.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_asyncio.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_cache.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_circular_imports.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_core.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_dns.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_engine.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_exceptions.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_history.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_init.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_listener.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_logger.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_services.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/test_updates.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/utils/__init__.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/utils/test_asyncio.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/utils/test_ipaddress.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/utils/test_name.py +0 -0
- {zeroconf-0.149.7 → zeroconf-0.149.8}/tests/utils/test_net.py +0 -0
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v0.149.8 (2026-05-19)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Bound NSEC bitmap length against record end
|
|
10
|
+
([#1731](https://github.com/python-zeroconf/python-zeroconf/pull/1731),
|
|
11
|
+
[`1d83550`](https://github.com/python-zeroconf/python-zeroconf/commit/1d83550c58ed0ea69b611a907cd4bdfcb2eef535))
|
|
12
|
+
|
|
13
|
+
### Testing
|
|
14
|
+
|
|
15
|
+
- Give IPv6-only loopback find() its own timeout
|
|
16
|
+
([#1721](https://github.com/python-zeroconf/python-zeroconf/pull/1721),
|
|
17
|
+
[`fcd1ffb`](https://github.com/python-zeroconf/python-zeroconf/commit/fcd1ffb4af9b3b26bc0ecae30641251a4c9a4ba6))
|
|
18
|
+
|
|
19
|
+
- Widen LOOPBACK_FIND_TIMEOUT under PyPy
|
|
20
|
+
([#1742](https://github.com/python-zeroconf/python-zeroconf/pull/1742),
|
|
21
|
+
[`6924606`](https://github.com/python-zeroconf/python-zeroconf/commit/69246065085fa25f9baf20abbf0eaddcb4a4d88c))
|
|
22
|
+
|
|
23
|
+
- Widen safety margin in test_response_aggregation_timings_multiple
|
|
24
|
+
([#1737](https://github.com/python-zeroconf/python-zeroconf/pull/1737),
|
|
25
|
+
[`228af17`](https://github.com/python-zeroconf/python-zeroconf/commit/228af178657a70dd2e76420a8187345de6ede46f))
|
|
26
|
+
|
|
27
|
+
|
|
5
28
|
## v0.149.7 (2026-05-18)
|
|
6
29
|
|
|
7
30
|
### Bug Fixes
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zeroconf"
|
|
7
|
-
version = "0.149.
|
|
7
|
+
version = "0.149.8"
|
|
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"
|
|
@@ -84,13 +84,13 @@ python = "^3.10"
|
|
|
84
84
|
ifaddr = ">=0.1.7"
|
|
85
85
|
|
|
86
86
|
[tool.poetry.group.dev.dependencies]
|
|
87
|
-
pytest = ">=
|
|
87
|
+
pytest = ">=9.0.3,<10.0"
|
|
88
88
|
pytest-cov = ">=4,<8"
|
|
89
|
-
pytest-asyncio = ">=
|
|
89
|
+
pytest-asyncio = ">=1.3.0,<1.4.0"
|
|
90
90
|
cython = "^3.2.4"
|
|
91
91
|
setuptools = ">=65.6.3,<83.0.0"
|
|
92
92
|
pytest-timeout = "^2.1.0"
|
|
93
|
-
pytest-codspeed = ">=
|
|
93
|
+
pytest-codspeed = ">=5.0.2,<6.0"
|
|
94
94
|
|
|
95
95
|
[tool.poetry.group.docs.dependencies]
|
|
96
96
|
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.149.
|
|
22
|
+
'version': '0.149.8',
|
|
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 is fork of pyzeroconf, Multicast DNS Service Discovery for Python,\noriginally by Paul Scott-Murphy (https://github.com/paulsm/pyzeroconf),\nmodified by William McBrine (https://github.com/wmcbrine/pyzeroconf).\n\nThe original William McBrine\'s fork note::\n\n This fork is used in all of my TiVo-related projects: HME for Python\n (and therefore HME/VLC), Network Remote, Remote Proxy, and pyTivo.\n Before this, I was tracking the changes for zeroconf.py in three\n separate repos. I figured I should have an authoritative source.\n\n Although I make changes based on my experience with TiVos, I expect that\n they\'re generally applicable. This version also includes patches found\n on the now-defunct (?) Launchpad repo of pyzeroconf, and elsewhere\n around the net -- not always well-documented, sorry.\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 an example of browsing for a service:\n\n.. code-block:: python\n\n from zeroconf import ServiceBrowser, ServiceListener, Zeroconf\n\n\n class MyListener(ServiceListener):\n\n def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n print(f"Service {name} updated")\n\n def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n print(f"Service {name} removed")\n\n def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n info = zc.get_service_info(type_, name)\n print(f"Service {name} added, service info: {info}")\n\n\n zeroconf = Zeroconf()\n listener = MyListener()\n browser = ServiceBrowser(zeroconf, "_http._tcp.local.", listener)\n try:\n input("Press enter to exit...\\n\\n")\n finally:\n zeroconf.close()\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\nIf you don\'t know the name of the service you need to browse for, 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\nLGPL, see COPYING file for details.\n',
|
|
25
25
|
'author': 'Paul Scott-Murphy',
|
|
@@ -88,7 +88,7 @@ from ._utils.time import ( # noqa # import needed for backwards compat
|
|
|
88
88
|
|
|
89
89
|
__author__ = "Paul Scott-Murphy, William McBrine"
|
|
90
90
|
__maintainer__ = "Jakub Stasiak <jakub@stasiak.at>"
|
|
91
|
-
__version__ = "0.149.
|
|
91
|
+
__version__ = "0.149.8"
|
|
92
92
|
__license__ = "LGPL"
|
|
93
93
|
|
|
94
94
|
|
|
@@ -388,9 +388,23 @@ class DNSIncoming:
|
|
|
388
388
|
offset = self.offset
|
|
389
389
|
offset_plus_one = offset + 1
|
|
390
390
|
offset_plus_two = offset + 2
|
|
391
|
+
# RFC 4034 §4.1.2: each window block is window-number byte +
|
|
392
|
+
# bitmap-length byte (1..32) + bitmap. A bitmap_length that walks
|
|
393
|
+
# past the record's declared end would otherwise leave self.offset
|
|
394
|
+
# pointing inside (or past) the next record header, corrupting
|
|
395
|
+
# every subsequent record in the same packet.
|
|
396
|
+
if offset_plus_two > end:
|
|
397
|
+
raise IncomingDecodeError(
|
|
398
|
+
f"NSEC bitmap window header truncated at offset {offset} from {self.source}"
|
|
399
|
+
)
|
|
391
400
|
window = view[offset]
|
|
392
401
|
bitmap_length = view[offset_plus_one]
|
|
393
402
|
bitmap_end = offset_plus_two + bitmap_length
|
|
403
|
+
if bitmap_length == 0 or bitmap_length > 32 or bitmap_end > end:
|
|
404
|
+
raise IncomingDecodeError(
|
|
405
|
+
f"NSEC bitmap length {bitmap_length} invalid or overruns record end "
|
|
406
|
+
f"at offset {offset} from {self.source}"
|
|
407
|
+
)
|
|
394
408
|
for i, byte in enumerate(self.data[offset_plus_two:bitmap_end]):
|
|
395
409
|
for bit in range(8):
|
|
396
410
|
if byte & (0x80 >> bit):
|
|
@@ -23,6 +23,7 @@ USA
|
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
25
|
import asyncio
|
|
26
|
+
import platform
|
|
26
27
|
import socket
|
|
27
28
|
import time
|
|
28
29
|
from functools import cache
|
|
@@ -35,6 +36,8 @@ from zeroconf._history import QuestionHistory
|
|
|
35
36
|
|
|
36
37
|
_MONOTONIC_RESOLUTION = time.get_clock_info("monotonic").resolution
|
|
37
38
|
|
|
39
|
+
_IS_PYPY = platform.python_implementation() == "PyPy"
|
|
40
|
+
|
|
38
41
|
# get_service_info / async_request timeout for tests using the
|
|
39
42
|
# `quick_request_timing` fixture. The fixture cuts the initial-query
|
|
40
43
|
# delay to ~15ms (10ms _LISTENER_TIME + 1-5ms jitter), so 50ms is
|
|
@@ -49,7 +52,17 @@ QUICK_REQUEST_TIMEOUT_MS = 50
|
|
|
49
52
|
# the `quick_timing` fixture, which shrinks the browser's first-query
|
|
50
53
|
# delay from RFC 6762 §5.2's 20-120ms window to 1-5ms; with that shave
|
|
51
54
|
# the registrar's response lands inside ~10ms and 75ms is ~7x headroom.
|
|
52
|
-
|
|
55
|
+
# PyPy's JIT is still warming up the first time this path runs early in
|
|
56
|
+
# the suite, so the round trip is too slow for 75ms; give it more room.
|
|
57
|
+
LOOPBACK_FIND_TIMEOUT = 0.3 if _IS_PYPY else 0.075
|
|
58
|
+
|
|
59
|
+
# IPv6-only `find()` on Linux GitHub runners can hit `[Errno 101] Network
|
|
60
|
+
# is unreachable` on the `::1` socket and falls back to the `fe80::` link-
|
|
61
|
+
# local interface, which adds latency the IPv4 loopback path never pays.
|
|
62
|
+
# PyPy widens that further with JIT warmup. The 75ms budget that works on
|
|
63
|
+
# IPv4 loopback is too tight for the V6Only path under those conditions
|
|
64
|
+
# — give it more headroom.
|
|
65
|
+
IPV6_LOOPBACK_FIND_TIMEOUT = 0.5
|
|
53
66
|
|
|
54
67
|
|
|
55
68
|
class QuestionHistoryWithoutSuppression(QuestionHistory):
|
|
@@ -11,7 +11,7 @@ import unittest
|
|
|
11
11
|
import zeroconf as r
|
|
12
12
|
from zeroconf import ServiceInfo, Zeroconf, ZeroconfServiceTypes
|
|
13
13
|
|
|
14
|
-
from .. import LOOPBACK_FIND_TIMEOUT, _clear_cache, has_working_ipv6
|
|
14
|
+
from .. import IPV6_LOOPBACK_FIND_TIMEOUT, LOOPBACK_FIND_TIMEOUT, _clear_cache, has_working_ipv6
|
|
15
15
|
|
|
16
16
|
log = logging.getLogger("zeroconf")
|
|
17
17
|
original_logging_level = logging.NOTSET
|
|
@@ -116,11 +116,11 @@ def test_integration_with_listener_ipv6(quick_timing, disable_duplicate_packet_s
|
|
|
116
116
|
zeroconf_registrar.registry.async_add(info)
|
|
117
117
|
try:
|
|
118
118
|
service_types = ZeroconfServiceTypes.find(
|
|
119
|
-
ip_version=r.IPVersion.V6Only, timeout=
|
|
119
|
+
ip_version=r.IPVersion.V6Only, timeout=IPV6_LOOPBACK_FIND_TIMEOUT
|
|
120
120
|
)
|
|
121
121
|
assert type_ in service_types
|
|
122
122
|
_clear_cache(zeroconf_registrar)
|
|
123
|
-
service_types = ZeroconfServiceTypes.find(zc=zeroconf_registrar, timeout=
|
|
123
|
+
service_types = ZeroconfServiceTypes.find(zc=zeroconf_registrar, timeout=IPV6_LOOPBACK_FIND_TIMEOUT)
|
|
124
124
|
assert type_ in service_types
|
|
125
125
|
|
|
126
126
|
finally:
|
|
@@ -1783,19 +1783,19 @@ async def test_response_aggregation_timings_multiple(run_isolated, disable_dupli
|
|
|
1783
1783
|
protocol.last_time = 0 # manually reset the last time to avoid duplicate packet suppression
|
|
1784
1784
|
protocol.datagram_received(query2.packets()[0], ("127.0.0.1", const._MDNS_PORT))
|
|
1785
1785
|
protocol.last_time = 0 # manually reset the last time to avoid duplicate packet suppression
|
|
1786
|
-
# The
|
|
1787
|
-
#
|
|
1788
|
-
#
|
|
1789
|
-
await asyncio.sleep(0.
|
|
1786
|
+
# The minimum protected send_after is 1000ms + 20ms random; sleep
|
|
1787
|
+
# well under that so coarse timers on slow runners cannot push the
|
|
1788
|
+
# send into this window and flake the assertion.
|
|
1789
|
+
await asyncio.sleep(0.5)
|
|
1790
1790
|
calls = send_mock.mock_calls
|
|
1791
1791
|
assert len(calls) == 0
|
|
1792
1792
|
|
|
1793
1793
|
# 1000ms (1s network protection delays)
|
|
1794
|
-
# -
|
|
1794
|
+
# - 500ms (already slept)
|
|
1795
1795
|
# + 120ms (maximum random delay)
|
|
1796
1796
|
# + 200ms (maximum protected aggregation delay)
|
|
1797
1797
|
# + 20ms (execution time)
|
|
1798
|
-
await asyncio.sleep(millis_to_seconds(1000 -
|
|
1798
|
+
await asyncio.sleep(millis_to_seconds(1000 - 500 + 120 + 200 + 20))
|
|
1799
1799
|
calls = send_mock.mock_calls
|
|
1800
1800
|
assert len(calls) == 1
|
|
1801
1801
|
outgoing = send_mock.call_args[0][0]
|
|
@@ -807,6 +807,85 @@ def test_parse_packet_with_nsec_record():
|
|
|
807
807
|
assert nsec_record.next_name == "MyHome54 (2)._meshcop._udp.local."
|
|
808
808
|
|
|
809
809
|
|
|
810
|
+
def test_nsec_bitmap_length_overruns_record_end():
|
|
811
|
+
"""Reject NSEC bitmap whose declared length runs past the record boundary."""
|
|
812
|
+
# 0 questions, 2 answers. Answer 1 is a malformed NSEC: rdlength=9, but the
|
|
813
|
+
# bitmap window claims length=255 — overrunning the record. Answer 2 is a
|
|
814
|
+
# PTR that must still parse because the offset for the next record stays
|
|
815
|
+
# pinned to the NSEC's declared end.
|
|
816
|
+
packet = (
|
|
817
|
+
b"\x00\x00\x84\x00\x00\x00\x00\x02\x00\x00\x00\x00"
|
|
818
|
+
b"\x04test\x05local\x00"
|
|
819
|
+
b"\x00\x2f\x80\x01"
|
|
820
|
+
b"\x00\x00\x11\x94"
|
|
821
|
+
b"\x00\x09"
|
|
822
|
+
b"\xc0\x0c"
|
|
823
|
+
b"\x00\xff"
|
|
824
|
+
b"\x80\x00\x00\x00\x00"
|
|
825
|
+
b"\xc0\x0c"
|
|
826
|
+
b"\x00\x0c\x00\x01"
|
|
827
|
+
b"\x00\x00\x11\x94"
|
|
828
|
+
b"\x00\x02"
|
|
829
|
+
b"\xc0\x0c"
|
|
830
|
+
)
|
|
831
|
+
parsed = r.DNSIncoming(packet)
|
|
832
|
+
answers = parsed.answers()
|
|
833
|
+
ptrs = [a for a in answers if isinstance(a, r.DNSPointer)]
|
|
834
|
+
assert len(ptrs) == 1
|
|
835
|
+
assert ptrs[0].alias == "test.local."
|
|
836
|
+
# The malformed NSEC must not surface — if it did, it would carry rdtypes
|
|
837
|
+
# synthesized from bytes past the record boundary.
|
|
838
|
+
assert not any(isinstance(a, r.DNSNsec) for a in answers)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def test_nsec_bitmap_zero_length_window_rejected():
|
|
842
|
+
"""A bitmap window with length=0 violates RFC 4034 §4.1.2 and must be rejected."""
|
|
843
|
+
packet = (
|
|
844
|
+
b"\x00\x00\x84\x00\x00\x00\x00\x02\x00\x00\x00\x00"
|
|
845
|
+
b"\x04test\x05local\x00"
|
|
846
|
+
b"\x00\x2f\x80\x01"
|
|
847
|
+
b"\x00\x00\x11\x94"
|
|
848
|
+
b"\x00\x04"
|
|
849
|
+
b"\xc0\x0c"
|
|
850
|
+
b"\x00\x00"
|
|
851
|
+
b"\xc0\x0c"
|
|
852
|
+
b"\x00\x0c\x00\x01"
|
|
853
|
+
b"\x00\x00\x11\x94"
|
|
854
|
+
b"\x00\x02"
|
|
855
|
+
b"\xc0\x0c"
|
|
856
|
+
)
|
|
857
|
+
parsed = r.DNSIncoming(packet)
|
|
858
|
+
answers = parsed.answers()
|
|
859
|
+
ptrs = [a for a in answers if isinstance(a, r.DNSPointer)]
|
|
860
|
+
assert len(ptrs) == 1
|
|
861
|
+
assert not any(isinstance(a, r.DNSNsec) for a in answers)
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def test_nsec_bitmap_truncated_window_header_rejected():
|
|
865
|
+
"""Reject NSEC bitmap with a trailing byte too short to hold a window header."""
|
|
866
|
+
packet = (
|
|
867
|
+
b"\x00\x00\x84\x00\x00\x00\x00\x02\x00\x00\x00\x00"
|
|
868
|
+
b"\x04test\x05local\x00"
|
|
869
|
+
b"\x00\x2f\x80\x01"
|
|
870
|
+
b"\x00\x00\x11\x94"
|
|
871
|
+
b"\x00\x06"
|
|
872
|
+
b"\xc0\x0c"
|
|
873
|
+
b"\x00\x01\x80"
|
|
874
|
+
b"\xff"
|
|
875
|
+
b"\xc0\x0c"
|
|
876
|
+
b"\x00\x0c\x00\x01"
|
|
877
|
+
b"\x00\x00\x11\x94"
|
|
878
|
+
b"\x00\x02"
|
|
879
|
+
b"\xc0\x0c"
|
|
880
|
+
)
|
|
881
|
+
parsed = r.DNSIncoming(packet)
|
|
882
|
+
answers = parsed.answers()
|
|
883
|
+
ptrs = [a for a in answers if isinstance(a, r.DNSPointer)]
|
|
884
|
+
assert len(ptrs) == 1
|
|
885
|
+
assert ptrs[0].alias == "test.local."
|
|
886
|
+
assert not any(isinstance(a, r.DNSNsec) for a in answers)
|
|
887
|
+
|
|
888
|
+
|
|
810
889
|
def test_records_same_packet_share_fate():
|
|
811
890
|
"""Test records in the same packet all have the same created time."""
|
|
812
891
|
out = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA)
|
|
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
|