| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,7 @@ def build(setup_kwargs: Any) -> None: | |||
| 25 | 25 | [ | |
| 26 | 26 | "src/zeroconf/_dns.py", | |
| 27 | 27 | "src/zeroconf/_cache.py", | |
| 28 | + "src/zeroconf/_history.py", | ||
| 28 | 29 | "src/zeroconf/_listener.py", | |
| 29 | 30 | "src/zeroconf/_protocol/incoming.py", | |
| 30 | 31 | "src/zeroconf/_protocol/outgoing.py", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,16 @@ | |||
| 1 | + import cython | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + cdef cython.double _DUPLICATE_QUESTION_INTERVAL | ||
| 5 | + | ||
| 6 | + cdef class QuestionHistory: | ||
| 7 | + | ||
| 8 | + cdef cython.dict _history | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + @cython.locals(than=cython.double, previous_question=cython.tuple, previous_known_answers=cython.set) | ||
| 12 | + cpdef suppresses(self, object question, cython.double now, cython.set known_answers) | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + @cython.locals(than=cython.double, now_known_answers=cython.tuple) | ||
| 16 | + cpdef async_expire(self, cython.double now) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,24 +20,29 @@ | |||
| 20 | 20 | USA | |
| 21 | 21 | """ | |
| 22 | 22 | ||
| 23 | - from typing import Dict, Set, Tuple | ||
| 23 | + from typing import Dict, List, Set, Tuple | ||
| 24 | 24 | ||
| 25 | 25 | from ._dns import DNSQuestion, DNSRecord | |
| 26 | 26 | from .const import _DUPLICATE_QUESTION_INTERVAL | |
| 27 | 27 | ||
| 28 | 28 | # The QuestionHistory is used to implement Duplicate Question Suppression | |
| 29 | 29 | # https://datatracker.ietf.org/doc/html/rfc6762#section-7.3 | |
| 30 | 30 | ||
| 31 | + _float = float | ||
| 32 | + | ||
| 31 | 33 | ||
| 32 | 34 | class QuestionHistory: | |
| 35 | + """Remember questions and known answers.""" | ||
| 36 | + | ||
| 33 | 37 | def __init__(self) -> None: | |
| 38 | + """Init a new QuestionHistory.""" | ||
| 34 | 39 | self._history: Dict[DNSQuestion, Tuple[float, Set[DNSRecord]]] = {} | |
| 35 | 40 | ||
| 36 | - def add_question_at_time(self, question: DNSQuestion, now: float, known_answers: Set[DNSRecord]) -> None: | ||
| 41 | + def add_question_at_time(self, question: DNSQuestion, now: _float, known_answers: Set[DNSRecord]) -> None: | ||
| 37 | 42 | """Remember a question with known answers.""" | |
| 38 | 43 | self._history[question] = (now, known_answers) | |
| 39 | 44 | ||
| 40 | - def suppresses(self, question: DNSQuestion, now: float, known_answers: Set[DNSRecord]) -> bool: | ||
| 45 | + def suppresses(self, question: DNSQuestion, now: _float, known_answers: Set[DNSRecord]) -> bool: | ||
| 41 | 46 | """Check to see if a question should be suppressed. | |
| 42 | 47 | ||
| 43 | 48 | https://datatracker.ietf.org/doc/html/rfc6762#section-7.3 | |
@@ -59,12 +64,16 @@ def suppresses(self, question: DNSQuestion, now: float, known_answers: Set[DNSRe | |||
| 59 | 64 | return False | |
| 60 | 65 | return True | |
| 61 | 66 | ||
| 62 | - def async_expire(self, now: float) -> None: | ||
| 67 | + def async_expire(self, now: _float) -> None: | ||
| 63 | 68 | """Expire the history of old questions.""" | |
| 64 | - removes = [ | ||
| 65 | - question | ||
| 66 | - for question, now_known_answers in self._history.items() | ||
| 67 | - if now - now_known_answers[0] > _DUPLICATE_QUESTION_INTERVAL | ||
| 68 | - ] | ||
| 69 | + removes: List[DNSQuestion] = [] | ||
| 70 | + for question, now_known_answers in self._history.items(): | ||
| 71 | + than, _ = now_known_answers | ||
| 72 | + if now - than > _DUPLICATE_QUESTION_INTERVAL: | ||
| 73 | + removes.append(question) | ||
| 69 | 74 | for question in removes: | |
| 70 | 75 | del self._history[question] | |
| 76 | + | ||
| 77 | + def clear(self) -> None: | ||
| 78 | + """Clear the history.""" | ||
| 79 | + self._history.clear() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,11 +23,17 @@ | |||
| 23 | 23 | import asyncio | |
| 24 | 24 | import socket | |
| 25 | 25 | from functools import lru_cache | |
| 26 | - from typing import List | ||
| 26 | + from typing import List, Set | ||
| 27 | 27 | ||
| 28 | 28 | import ifaddr | |
| 29 | 29 | ||
| 30 | - from zeroconf import DNSIncoming, Zeroconf | ||
| 30 | + from zeroconf import DNSIncoming, DNSQuestion, DNSRecord, Zeroconf | ||
| 31 | + from zeroconf._history import QuestionHistory | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + class QuestionHistoryWithoutSuppression(QuestionHistory): | ||
| 35 | + def suppresses(self, question: DNSQuestion, now: float, known_answers: Set[DNSRecord]) -> bool: | ||
| 36 | + return False | ||
| 31 | 37 | ||
| 32 | 38 | ||
| 33 | 39 | def _inject_responses(zc: Zeroconf, msgs: List[DNSIncoming]) -> None: | |
@@ -77,4 +83,4 @@ def has_working_ipv6(): | |||
| 77 | 83 | ||
| 78 | 84 | def _clear_cache(zc): | |
| 79 | 85 | zc.cache.cache.clear() | |
| 80 | - zc.question_history._history.clear() | ||
| 86 | + zc.question_history.clear() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -31,7 +31,12 @@ | |||
| 31 | 31 | from zeroconf._services.info import ServiceInfo | |
| 32 | 32 | from zeroconf.asyncio import AsyncZeroconf | |
| 33 | 33 | ||
| 34 | - from .. import _inject_response, _wait_for_start, has_working_ipv6 | ||
| 34 | + from .. import ( | ||
| 35 | + QuestionHistoryWithoutSuppression, | ||
| 36 | + _inject_response, | ||
| 37 | + _wait_for_start, | ||
| 38 | + has_working_ipv6, | ||
| 39 | + ) | ||
| 35 | 40 | ||
| 36 | 41 | log = logging.getLogger('zeroconf') | |
| 37 | 42 | original_logging_level = logging.NOTSET | |
@@ -444,6 +449,7 @@ def test_backoff(): | |||
| 444 | 449 | type_ = "_http._tcp.local." | |
| 445 | 450 | zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) | |
| 446 | 451 | _wait_for_start(zeroconf_browser) | |
| 452 | + zeroconf_browser.question_history = QuestionHistoryWithoutSuppression() | ||
| 447 | 453 | ||
| 448 | 454 | # we are going to patch the zeroconf send to check query transmission | |
| 449 | 455 | old_send = zeroconf_browser.async_send | |
@@ -465,10 +471,8 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT, v6_flow_scope=()): | |||
| 465 | 471 | # patch the zeroconf current_time_millis | |
| 466 | 472 | # patch the backoff limit to prevent test running forever | |
| 467 | 473 | with patch.object(zeroconf_browser, "async_send", send), patch.object( | |
| 468 | - zeroconf_browser.question_history, "suppresses", return_value=False | ||
| 469 | - ), patch.object(_services_browser, "current_time_millis", current_time_millis), patch.object( | ||
| 470 | - _services_browser, "_BROWSER_BACKOFF_LIMIT", 10 | ||
| 471 | - ), patch.object( | ||
| 474 | + _services_browser, "current_time_millis", current_time_millis | ||
| 475 | + ), patch.object(_services_browser, "_BROWSER_BACKOFF_LIMIT", 10), patch.object( | ||
| 472 | 476 | _services_browser, "_FIRST_QUERY_DELAY_RANDOM_INTERVAL", (0, 0) | |
| 473 | 477 | ): | |
| 474 | 478 | # dummy service callback | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,7 +43,7 @@ | |||
| 43 | 43 | ) | |
| 44 | 44 | from zeroconf.const import _LISTENER_TIME | |
| 45 | 45 | ||
| 46 | - from . import _clear_cache, has_working_ipv6 | ||
| 46 | + from . import QuestionHistoryWithoutSuppression, _clear_cache, has_working_ipv6 | ||
| 47 | 47 | ||
| 48 | 48 | log = logging.getLogger('zeroconf') | |
| 49 | 49 | original_logging_level = logging.NOTSET | |
@@ -951,6 +951,7 @@ def on_service_state_change(zeroconf, service_type, state_change, name): | |||
| 951 | 951 | ||
| 952 | 952 | aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) | |
| 953 | 953 | zeroconf_browser = aiozc.zeroconf | |
| 954 | + zeroconf_browser.question_history = QuestionHistoryWithoutSuppression() | ||
| 954 | 955 | await zeroconf_browser.async_wait_for_start() | |
| 955 | 956 | ||
| 956 | 957 | # we are going to patch the zeroconf send to check packet sizes | |
@@ -990,11 +991,9 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT, v6_flow_scope=()): | |||
| 990 | 991 | # patch the backoff limit to ensure we always get one query every 1/4 of the DNS TTL | |
| 991 | 992 | # Disable duplicate question suppression and duplicate packet suppression for this test as it works | |
| 992 | 993 | # by asking the same question over and over | |
| 993 | - with patch.object(zeroconf_browser.question_history, "suppresses", return_value=False), patch.object( | ||
| 994 | - zeroconf_browser, "async_send", send | ||
| 995 | - ), patch("zeroconf._services.browser.current_time_millis", _new_current_time_millis), patch.object( | ||
| 996 | - _services_browser, "_BROWSER_BACKOFF_LIMIT", int(expected_ttl / 4) | ||
| 997 | - ): | ||
| 994 | + with patch.object(zeroconf_browser, "async_send", send), patch( | ||
| 995 | + "zeroconf._services.browser.current_time_millis", _new_current_time_millis | ||
| 996 | + ), patch.object(_services_browser, "_BROWSER_BACKOFF_LIMIT", int(expected_ttl / 4)): | ||
| 998 | 997 | service_added = asyncio.Event() | |
| 999 | 998 | service_removed = asyncio.Event() | |
| 1000 | 999 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1131,7 +1131,7 @@ async def test_cache_flush_bit(): | |||
| 1131 | 1131 | for record in new_records: | |
| 1132 | 1132 | assert zc.cache.async_get_unique(record) is not None | |
| 1133 | 1133 | ||
| 1134 | - original_a_record.created = current_time_millis() - 1001 | ||
| 1134 | + original_a_record.created = current_time_millis() - 1500 | ||
| 1135 | 1135 | ||
| 1136 | 1136 | # Do the run within 1s to verify the original record is not going to be expired | |
| 1137 | 1137 | out = r.DNSOutgoing(const._FLAGS_QR_RESPONSE | const._FLAGS_AA, multicast=True) | |
@@ -1146,7 +1146,7 @@ async def test_cache_flush_bit(): | |||
| 1146 | 1146 | cached_records = [zc.cache.async_get_unique(record) for record in new_records] | |
| 1147 | 1147 | for cached_record in cached_records: | |
| 1148 | 1148 | assert cached_record is not None | |
| 1149 | - cached_record.created = current_time_millis() - 1001 | ||
| 1149 | + cached_record.created = current_time_millis() - 1500 | ||
| 1150 | 1150 | ||
| 1151 | 1151 | fresh_address = socket.inet_aton("4.4.4.4") | |
| 1152 | 1152 | info.addresses = [fresh_address] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,8 @@ | |||
| 14 | 14 | from zeroconf._protocol import outgoing | |
| 15 | 15 | from zeroconf._protocol.incoming import DNSIncoming | |
| 16 | 16 | ||
| 17 | + from . import QuestionHistoryWithoutSuppression | ||
| 18 | + | ||
| 17 | 19 | log = logging.getLogger('zeroconf') | |
| 18 | 20 | original_logging_level = logging.NOTSET | |
| 19 | 21 | ||
@@ -123,6 +125,7 @@ def test_guard_against_duplicate_packets(): | |||
| 123 | 125 | These packets can quickly overwhelm the system. | |
| 124 | 126 | """ | |
| 125 | 127 | zc = Zeroconf(interfaces=['127.0.0.1']) | |
| 128 | + zc.question_history = QuestionHistoryWithoutSuppression() | ||
| 126 | 129 | ||
| 127 | 130 | class SubListener(_listener.AsyncListener): | |
| 128 | 131 | def handle_query_or_defer( | |
| Back | FazBrowse Home | New Git URL |
0 commit comments