| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,7 @@ | |||
| 2 | 2 | import cython | |
| 3 | 3 | ||
| 4 | 4 | from .._cache cimport DNSCache | |
| 5 | - from .._dns cimport DNSAddress, DNSPointer, DNSQuestion, DNSRecord, DNSRRSet | ||
| 5 | + from .._dns cimport DNSAddress, DNSNsec, DNSPointer, DNSQuestion, DNSRecord, DNSRRSet | ||
| 6 | 6 | from .._history cimport QuestionHistory | |
| 7 | 7 | from .._protocol.incoming cimport DNSIncoming | |
| 8 | 8 | from .._services.info cimport ServiceInfo | |
@@ -83,7 +83,12 @@ cdef class QueryHandler: | |||
| 83 | 83 | @cython.locals(service=ServiceInfo) | |
| 84 | 84 | cdef void _add_pointer_answers(self, list services, cython.dict answer_set, DNSRRSet known_answers) | |
| 85 | 85 | ||
| 86 | - @cython.locals(service=ServiceInfo, dns_address=DNSAddress) | ||
| 86 | + @cython.locals( | ||
| 87 | + service=ServiceInfo, | ||
| 88 | + dns_address=DNSAddress, | ||
| 89 | + type_seen=cython.bint, | ||
| 90 | + nsec=DNSNsec, | ||
| 91 | + ) | ||
| 87 | 92 | cdef void _add_address_answers(self, list services, cython.dict answer_set, DNSRRSet known_answers, cython.uint type_) | |
| 88 | 93 | ||
| 89 | 94 | @cython.locals(question_lower_name=str, type_=cython.uint, service=ServiceInfo) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -264,23 +264,24 @@ def _add_address_answers( | |||
| 264 | 264 | for service in services: | |
| 265 | 265 | answers: list[DNSAddress] = [] | |
| 266 | 266 | additionals: set[DNSRecord] = set() | |
| 267 | - seen_types: set[int] = set() | ||
| 267 | + type_seen = False | ||
| 268 | 268 | for dns_address in service._dns_addresses(None, _IPVersion_ALL): | |
| 269 | - seen_types.add(dns_address.type) | ||
| 270 | 269 | if dns_address.type != type_: | |
| 271 | 270 | additionals.add(dns_address) | |
| 272 | - elif not known_answers.suppresses(dns_address): | ||
| 273 | - answers.append(dns_address) | ||
| 274 | - missing_types: set[int] = _ADDRESS_RECORD_TYPES - seen_types | ||
| 271 | + else: | ||
| 272 | + type_seen = True | ||
| 273 | + if not known_answers.suppresses(dns_address): | ||
| 274 | + answers.append(dns_address) | ||
| 275 | 275 | if answers: | |
| 276 | - if missing_types: | ||
| 277 | - assert service.server is not None, "Service server must be set for NSEC record." | ||
| 278 | - additionals.add(service._dns_nsec(list(missing_types), None)) | ||
| 276 | + nsec = service._dns_address_nsec(None) | ||
| 277 | + if nsec is not None: | ||
| 278 | + additionals.add(nsec) | ||
| 279 | 279 | for answer in answers: | |
| 280 | 280 | answer_set[answer] = additionals | |
| 281 | - elif type_ in missing_types: | ||
| 282 | - assert service.server is not None, "Service server must be set for NSEC record." | ||
| 283 | - answer_set[service._dns_nsec(list(missing_types), None)] = set() | ||
| 281 | + elif not type_seen and type_ in _ADDRESS_RECORD_TYPES: | ||
| 282 | + nsec = service._dns_address_nsec(None) | ||
| 283 | + if nsec is not None: | ||
| 284 | + answer_set[nsec] = set() | ||
| 284 | 285 | ||
| 285 | 286 | def _answer_question( | |
| 286 | 287 | self, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -45,8 +45,6 @@ cdef object QM_QUESTION | |||
| 45 | 45 | cdef object _IPVersion_All_value | |
| 46 | 46 | cdef object _IPVersion_V4Only_value | |
| 47 | 47 | ||
| 48 | - cdef cython.set _ADDRESS_RECORD_TYPES | ||
| 49 | - | ||
| 50 | 48 | cdef unsigned int _DUPLICATE_QUESTION_INTERVAL | |
| 51 | 49 | ||
| 52 | 50 | cdef bint TYPE_CHECKING | |
@@ -77,6 +75,7 @@ cdef class ServiceInfo(RecordUpdateListener): | |||
| 77 | 75 | cdef public DNSService _dns_service_cache | |
| 78 | 76 | cdef public DNSText _dns_text_cache | |
| 79 | 77 | cdef public cython.list _dns_address_cache | |
| 78 | + cdef public DNSNsec _dns_address_nsec_cache | ||
| 80 | 79 | cdef public cython.set _get_address_and_nsec_records_cache | |
| 81 | 80 | cdef public cython.set _query_record_types | |
| 82 | 81 | cdef public bint _txt_seen | |
@@ -111,10 +110,14 @@ cdef class ServiceInfo(RecordUpdateListener): | |||
| 111 | 110 | @cython.locals( | |
| 112 | 111 | dns_service_record=DNSService, | |
| 113 | 112 | dns_text_record=DNSText, | |
| 114 | - dns_address_record=DNSAddress | ||
| 113 | + dns_address_record=DNSAddress, | ||
| 114 | + dns_nsec_record=DNSNsec | ||
| 115 | 115 | ) | |
| 116 | 116 | cdef bint _process_record_threadsafe(self, object zc, DNSRecord record, double now) | |
| 117 | 117 | ||
| 118 | + @cython.locals(rdtypes=cython.list) | ||
| 119 | + cdef bint _process_nsec_record(self, DNSNsec record) | ||
| 120 | + | ||
| 118 | 121 | @cython.locals(existing_idx=int, existing=object) | |
| 119 | 122 | cdef bint _upsert_ipv6_address(self, object ip_addr) | |
| 120 | 123 | ||
@@ -143,7 +146,8 @@ cdef class ServiceInfo(RecordUpdateListener): | |||
| 143 | 146 | @cython.locals(cacheable=cython.bint) | |
| 144 | 147 | cdef DNSText _dns_text(self, object override_ttl) | |
| 145 | 148 | ||
| 146 | - cdef DNSNsec _dns_nsec(self, cython.list missing_types, object override_ttl) | ||
| 149 | + @cython.locals(cacheable=cython.bint, has_v4=cython.bint, has_v6=cython.bint) | ||
| 150 | + cdef DNSNsec _dns_address_nsec(self, object override_ttl) | ||
| 147 | 151 | ||
| 148 | 152 | @cython.locals(cacheable=cython.bint) | |
| 149 | 153 | cdef cython.set _get_address_and_nsec_records(self, object override_ttl) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,6 +24,7 @@ | |||
| 24 | 24 | ||
| 25 | 25 | import asyncio | |
| 26 | 26 | import random | |
| 27 | + import warnings | ||
| 27 | 28 | from collections.abc import Sequence | |
| 28 | 29 | from typing import TYPE_CHECKING, cast | |
| 29 | 30 | ||
@@ -62,7 +63,6 @@ | |||
| 62 | 63 | from .._utils.net import IPVersion, _encode_address | |
| 63 | 64 | from .._utils.time import current_time_millis | |
| 64 | 65 | from ..const import ( | |
| 65 | - _ADDRESS_RECORD_TYPES, | ||
| 66 | 66 | _CLASS_IN, | |
| 67 | 67 | _CLASS_IN_UNIQUE, | |
| 68 | 68 | _DNS_HOST_TTL, | |
@@ -180,6 +180,7 @@ class ServiceInfo(RecordUpdateListener): | |||
| 180 | 180 | __slots__ = ( | |
| 181 | 181 | "_decoded_properties", | |
| 182 | 182 | "_dns_address_cache", | |
| 183 | + "_dns_address_nsec_cache", | ||
| 183 | 184 | "_dns_pointer_cache", | |
| 184 | 185 | "_dns_service_cache", | |
| 185 | 186 | "_dns_text_cache", | |
@@ -255,6 +256,7 @@ def __init__( | |||
| 255 | 256 | self.other_ttl = other_ttl | |
| 256 | 257 | self._new_records_futures: set[asyncio.Future] | None = None | |
| 257 | 258 | self._dns_address_cache: list[DNSAddress] | None = None | |
| 259 | + self._dns_address_nsec_cache: DNSNsec | None = None | ||
| 258 | 260 | self._dns_pointer_cache: DNSPointer | None = None | |
| 259 | 261 | self._dns_service_cache: DNSService | None = None | |
| 260 | 262 | self._dns_text_cache: DNSText | None = None | |
@@ -294,6 +296,7 @@ def addresses(self, value: list[bytes]) -> None: | |||
| 294 | 296 | self._ipv4_addresses.clear() | |
| 295 | 297 | self._ipv6_addresses.clear() | |
| 296 | 298 | self._dns_address_cache = None | |
| 299 | + self._dns_address_nsec_cache = None | ||
| 297 | 300 | self._get_address_and_nsec_records_cache = None | |
| 298 | 301 | ||
| 299 | 302 | for address in value: | |
@@ -336,6 +339,7 @@ def decoded_properties(self) -> dict[str, str | None]: | |||
| 336 | 339 | def async_clear_cache(self) -> None: | |
| 337 | 340 | """Clear the cache for this service info.""" | |
| 338 | 341 | self._dns_address_cache = None | |
| 342 | + self._dns_address_nsec_cache = None | ||
| 339 | 343 | self._dns_pointer_cache = None | |
| 340 | 344 | self._dns_service_cache = None | |
| 341 | 345 | self._dns_text_cache = None | |
@@ -644,8 +648,26 @@ def _process_record_threadsafe(self, zc: Zeroconf, record: DNSRecord, now: float | |||
| 644 | 648 | self._set_ipv6_addresses_from_cache(zc, now) | |
| 645 | 649 | return True | |
| 646 | 650 | ||
| 651 | + if record_type is DNSNsec: | ||
| 652 | + dns_nsec_record = record | ||
| 653 | + if TYPE_CHECKING: | ||
| 654 | + assert isinstance(dns_nsec_record, DNSNsec) | ||
| 655 | + return self._process_nsec_record(dns_nsec_record) | ||
| 656 | + | ||
| 647 | 657 | return False | |
| 648 | 658 | ||
| 659 | + def _process_nsec_record(self, record: DNSNsec) -> bool: | ||
| 660 | + """Record a TXT denial from an NSEC record at the service name.""" | ||
| 661 | + rdtypes = record.rdtypes | ||
| 662 | + # RFC 6762 §6.1: the type bitmap lists the rrtypes that exist, so SRV | ||
| 663 | + # present with TXT absent denies the TXT record. Requiring the SRV bit | ||
| 664 | + # also keeps older python-zeroconf NSECs, which listed the missing | ||
| 665 | + # address types, from being misread as a TXT denial. | ||
| 666 | + if self._txt_seen or _TYPE_SRV not in rdtypes or _TYPE_TXT in rdtypes: | ||
| 667 | + return False | ||
| 668 | + self._txt_seen = True | ||
| 669 | + return True | ||
| 670 | + | ||
| 649 | 671 | def dns_addresses( | |
| 650 | 672 | self, | |
| 651 | 673 | override_ttl: int_ | None = None, | |
@@ -751,39 +773,58 @@ def _dns_text(self, override_ttl: int_ | None) -> DNSText: | |||
| 751 | 773 | self._dns_text_cache = record | |
| 752 | 774 | return record | |
| 753 | 775 | ||
| 754 | - def dns_nsec(self, missing_types: list[int], override_ttl: int_ | None = None) -> DNSNsec: | ||
| 755 | - """Return DNSNsec from ServiceInfo.""" | ||
| 756 | - return self._dns_nsec(missing_types, override_ttl) | ||
| 776 | + def dns_nsec(self, missing_types: list[int], override_ttl: int_ | None = None) -> DNSNsec | None: | ||
| 777 | + """Deprecated: use dns_address_nsec instead; missing_types is ignored.""" | ||
| 778 | + warnings.warn( | ||
| 779 | + "dns_nsec is deprecated, and will be removed in a future version. " | ||
| 780 | + "Use dns_address_nsec instead; missing_types is ignored.", | ||
| 781 | + DeprecationWarning, | ||
| 782 | + stacklevel=2, | ||
| 783 | + ) | ||
| 784 | + return self._dns_address_nsec(override_ttl) | ||
| 785 | + | ||
| 786 | + def dns_address_nsec(self, override_ttl: int_ | None = None) -> DNSNsec | None: | ||
| 787 | + """Return DNSNsec asserting which address types exist, or None if not applicable.""" | ||
| 788 | + return self._dns_address_nsec(override_ttl) | ||
| 757 | 789 | ||
| 758 | - def _dns_nsec(self, missing_types: list[int_], override_ttl: int_ | None) -> DNSNsec: | ||
| 759 | - """Return DNSNsec from ServiceInfo.""" | ||
| 760 | - return DNSNsec( | ||
| 761 | - self._name, | ||
| 790 | + def _dns_address_nsec(self, override_ttl: int_ | None) -> DNSNsec | None: | ||
| 791 | + cacheable = override_ttl is None | ||
| 792 | + if self._dns_address_nsec_cache is not None and cacheable: | ||
| 793 | + return self._dns_address_nsec_cache | ||
| 794 | + # RFC 6762 §6.1: the type bitmap lists the rrtypes that exist at the | ||
| 795 | + # name. Both families present leaves nothing to deny; neither leaves | ||
| 796 | + # nothing to assert (an empty bitmap is unencodable). | ||
| 797 | + has_v4 = bool(self._ipv4_addresses) | ||
| 798 | + has_v6 = bool(self._ipv6_addresses) | ||
| 799 | + if has_v4 == has_v6: | ||
| 800 | + return None | ||
| 801 | + assert self.server is not None, "Service server must be set for NSEC record." | ||
| 802 | + record = DNSNsec( | ||
| 803 | + self.server, | ||
| 762 | 804 | _TYPE_NSEC, | |
| 763 | 805 | _CLASS_IN_UNIQUE, | |
| 764 | 806 | override_ttl if override_ttl is not None else self.host_ttl, | |
| 765 | - self._name, | ||
| 766 | - missing_types, | ||
| 807 | + self.server, | ||
| 808 | + [_TYPE_A] if has_v4 else [_TYPE_AAAA], | ||
| 767 | 809 | 0.0, | |
| 768 | 810 | ) | |
| 811 | + if cacheable: | ||
| 812 | + self._dns_address_nsec_cache = record | ||
| 813 | + return record | ||
| 769 | 814 | ||
| 770 | 815 | def get_address_and_nsec_records(self, override_ttl: int_ | None = None) -> set[DNSRecord]: | |
| 771 | - """Build a set of address records and NSEC records for non-present record types.""" | ||
| 816 | + """Build a set of address records plus an NSEC asserting which address types exist.""" | ||
| 772 | 817 | return self._get_address_and_nsec_records(override_ttl) | |
| 773 | 818 | ||
| 774 | 819 | def _get_address_and_nsec_records(self, override_ttl: int_ | None) -> set[DNSRecord]: | |
| 775 | - """Build a set of address records and NSEC records for non-present record types.""" | ||
| 820 | + """Build a set of address records plus an NSEC asserting which address types exist.""" | ||
| 776 | 821 | cacheable = override_ttl is None | |
| 777 | 822 | if self._get_address_and_nsec_records_cache is not None and cacheable: | |
| 778 | 823 | return self._get_address_and_nsec_records_cache | |
| 779 | - missing_types: set[int] = _ADDRESS_RECORD_TYPES.copy() | ||
| 780 | - records: set[DNSRecord] = set() | ||
| 781 | - for dns_address in self._dns_addresses(override_ttl, IPVersion.All): | ||
| 782 | - missing_types.discard(dns_address.type) | ||
| 783 | - records.add(dns_address) | ||
| 784 | - if missing_types: | ||
| 785 | - assert self.server is not None, "Service server must be set for NSEC record." | ||
| 786 | - records.add(self._dns_nsec(list(missing_types), override_ttl)) | ||
| 824 | + records: set[DNSRecord] = set(self._dns_addresses(override_ttl, IPVersion.All)) | ||
| 825 | + nsec = self._dns_address_nsec(override_ttl) | ||
| 826 | + if nsec is not None: | ||
| 827 | + records.add(nsec) | ||
| 787 | 828 | if cacheable: | |
| 788 | 829 | self._get_address_and_nsec_records_cache = records | |
| 789 | 830 | return records | |
@@ -831,6 +872,10 @@ def _load_from_cache(self, zc: Zeroconf, now: float_) -> bool: | |||
| 831 | 872 | cached_txt_record = cache.get_by_details(self._name, _TYPE_TXT, _CLASS_IN) | |
| 832 | 873 | if cached_txt_record: | |
| 833 | 874 | self._process_record_threadsafe(zc, cached_txt_record, now) | |
| 875 | + if not self._txt_seen: | ||
| 876 | + cached_nsec_record = cache.get_by_details(self._name, _TYPE_NSEC, _CLASS_IN) | ||
| 877 | + if cached_nsec_record: | ||
| 878 | + self._process_record_threadsafe(zc, cached_nsec_record, now) | ||
| 834 | 879 | if original_server_key == self.server_key: | |
| 835 | 880 | # If there is a srv which changes the server_key, | |
| 836 | 881 | # A and AAAA will already be loaded from the cache | |
@@ -847,7 +892,8 @@ def _is_complete(self) -> bool: | |||
| 847 | 892 | ||
| 848 | 893 | RFC 6763 section 6 requires every DNS-SD service to have a TXT record, | |
| 849 | 894 | so a service is not complete until one has been seen. An empty TXT | |
| 850 | - record counts as seen; a missing one does not. | ||
| 895 | + record counts as seen, as does an NSEC record denying its existence | ||
| 896 | + (RFC 6762 section 6.1); a missing one does not. | ||
| 851 | 897 | """ | |
| 852 | 898 | return bool(self._txt_seen and (self._ipv4_addresses or self._ipv6_addresses)) | |
| 853 | 899 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments