| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -60,6 +60,8 @@ cdef class ServiceInfo(RecordUpdateListener): | |||
| 60 | 60 | cdef public str key | |
| 61 | 61 | cdef public cython.list _ipv4_addresses | |
| 62 | 62 | cdef public cython.list _ipv6_addresses | |
| 63 | + cdef public bint _ipv4_denied | ||
| 64 | + cdef public bint _ipv6_denied | ||
| 63 | 65 | cdef public object port | |
| 64 | 66 | cdef public object weight | |
| 65 | 67 | cdef public object priority | |
@@ -80,7 +82,7 @@ cdef class ServiceInfo(RecordUpdateListener): | |||
| 80 | 82 | cdef public cython.set _query_record_types | |
| 81 | 83 | cdef public bint _txt_seen | |
| 82 | 84 | ||
| 83 | - @cython.locals(record_update=RecordUpdate, update=bint, cache=DNSCache) | ||
| 85 | + @cython.locals(record_update=RecordUpdate, record=DNSRecord, nsec_records=list) | ||
| 84 | 86 | cpdef void async_update_records(self, object zc, double now, cython.list records) | |
| 85 | 87 | ||
| 86 | 88 | @cython.locals(cache=DNSCache) | |
@@ -115,8 +117,16 @@ cdef class ServiceInfo(RecordUpdateListener): | |||
| 115 | 117 | ) | |
| 116 | 118 | cdef bint _process_record_threadsafe(self, object zc, DNSRecord record, double now) | |
| 117 | 119 | ||
| 118 | - @cython.locals(rdtypes=cython.list) | ||
| 119 | - cdef bint _process_nsec_record(self, DNSNsec record) | ||
| 120 | + @cython.locals(cache=DNSCache) | ||
| 121 | + cdef void _load_records_for_new_server_from_cache(self, object zc, double now) | ||
| 122 | + | ||
| 123 | + @cython.locals( | ||
| 124 | + rdtypes=cython.list, | ||
| 125 | + updated=cython.bint, | ||
| 126 | + ipv4_denied=cython.bint, | ||
| 127 | + ipv6_denied=cython.bint, | ||
| 128 | + ) | ||
| 129 | + cdef bint _process_nsec_record(self, DNSNsec record, str record_key) | ||
| 120 | 130 | ||
| 121 | 131 | @cython.locals(existing_idx=int, existing=object) | |
| 122 | 132 | cdef bint _upsert_ipv6_address(self, object ip_addr) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -186,7 +186,9 @@ class ServiceInfo(RecordUpdateListener): | |||
| 186 | 186 | "_dns_text_cache", | |
| 187 | 187 | "_get_address_and_nsec_records_cache", | |
| 188 | 188 | "_ipv4_addresses", | |
| 189 | + "_ipv4_denied", | ||
| 189 | 190 | "_ipv6_addresses", | |
| 191 | + "_ipv6_denied", | ||
| 190 | 192 | "_name", | |
| 191 | 193 | "_new_records_futures", | |
| 192 | 194 | "_properties", | |
@@ -233,6 +235,8 @@ def __init__( | |||
| 233 | 235 | self.key = name.lower() | |
| 234 | 236 | self._ipv4_addresses: list[ZeroconfIPv4Address] = [] | |
| 235 | 237 | self._ipv6_addresses: list[ZeroconfIPv6Address] = [] | |
| 238 | + self._ipv4_denied = False | ||
| 239 | + self._ipv6_denied = False | ||
| 236 | 240 | if addresses is not None: | |
| 237 | 241 | self.addresses = addresses | |
| 238 | 242 | elif parsed_addresses is not None: | |
@@ -571,8 +575,21 @@ def async_update_records(self, zc: Zeroconf, now: float_, records: list[RecordUp | |||
| 571 | 575 | """ | |
| 572 | 576 | new_records_futures = self._new_records_futures | |
| 573 | 577 | updated: bool = False | |
| 578 | + nsec_records = None | ||
| 574 | 579 | for record_update in records: | |
| 575 | - updated |= self._process_record_threadsafe(zc, record_update.new, now) | ||
| 580 | + record = record_update.new | ||
| 581 | + # NSEC records are processed last so a denial for an SRV target | ||
| 582 | + # learned later in the same batch is not discarded (wire order of | ||
| 583 | + # records in a response is not guaranteed). | ||
| 584 | + if type(record) is DNSNsec: | ||
| 585 | + if nsec_records is None: | ||
| 586 | + nsec_records = [] | ||
| 587 | + nsec_records.append(record) | ||
| 588 | + continue | ||
| 589 | + updated |= self._process_record_threadsafe(zc, record, now) | ||
| 590 | + if nsec_records is not None: | ||
| 591 | + for record in nsec_records: | ||
| 592 | + updated |= self._process_record_threadsafe(zc, record, now) | ||
| 576 | 593 | if updated and new_records_futures: | |
| 577 | 594 | _resolve_all_futures_to_none(new_records_futures) | |
| 578 | 595 | ||
@@ -620,6 +637,14 @@ def _process_record_threadsafe(self, zc: Zeroconf, record: DNSRecord, now: float | |||
| 620 | 637 | assert isinstance(ip_addr, ZeroconfIPv6Address) | |
| 621 | 638 | return self._upsert_ipv6_address(ip_addr) | |
| 622 | 639 | ||
| 640 | + if record_type is DNSNsec: | ||
| 641 | + if record_key not in (self.server_key, self.key): | ||
| 642 | + return False | ||
| 643 | + dns_nsec_record = record | ||
| 644 | + if TYPE_CHECKING: | ||
| 645 | + assert isinstance(dns_nsec_record, DNSNsec) | ||
| 646 | + return self._process_nsec_record(dns_nsec_record, record_key) | ||
| 647 | + | ||
| 623 | 648 | if record_key != self.key: | |
| 624 | 649 | return False | |
| 625 | 650 | ||
@@ -644,29 +669,48 @@ def _process_record_threadsafe(self, zc: Zeroconf, record: DNSRecord, now: float | |||
| 644 | 669 | self.weight = dns_service_record.weight | |
| 645 | 670 | self.priority = dns_service_record.priority | |
| 646 | 671 | if old_server_key != self.server_key: | |
| 647 | - self._set_ipv4_addresses_from_cache(zc, now) | ||
| 648 | - self._set_ipv6_addresses_from_cache(zc, now) | ||
| 672 | + self._load_records_for_new_server_from_cache(zc, now) | ||
| 649 | 673 | return True | |
| 650 | 674 | ||
| 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 | - | ||
| 657 | 675 | return False | |
| 658 | 676 | ||
| 659 | - def _process_nsec_record(self, record: DNSNsec) -> bool: | ||
| 660 | - """Record a TXT denial from an NSEC record at the service name.""" | ||
| 677 | + def _load_records_for_new_server_from_cache(self, zc: Zeroconf, now: float_) -> None: | ||
| 678 | + """Re-derive per-host state, denials included, after the SRV target changed.""" | ||
| 679 | + self._ipv4_denied = False | ||
| 680 | + self._ipv6_denied = False | ||
| 681 | + self._set_ipv4_addresses_from_cache(zc, now) | ||
| 682 | + self._set_ipv6_addresses_from_cache(zc, now) | ||
| 683 | + if TYPE_CHECKING: | ||
| 684 | + assert self.server_key is not None | ||
| 685 | + cache = zc.cache | ||
| 686 | + cached_server_nsec_record = cache.get_by_details(self.server_key, _TYPE_NSEC, _CLASS_IN) | ||
| 687 | + if cached_server_nsec_record: | ||
| 688 | + self._process_record_threadsafe(zc, cached_server_nsec_record, now) | ||
| 689 | + | ||
| 690 | + def _process_nsec_record(self, record: DNSNsec, record_key: str_) -> bool: | ||
| 691 | + """Record the denials asserted by an NSEC record (RFC 6762 §6.1).""" | ||
| 661 | 692 | 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 | ||
| 693 | + updated = False | ||
| 694 | + if record_key == self.server_key: | ||
| 695 | + # Each NSEC is an authoritative snapshot of what exists at the | ||
| 696 | + # host, so recompute both flags instead of accumulating denials. | ||
| 697 | + ipv4_denied = _TYPE_A not in rdtypes | ||
| 698 | + ipv6_denied = _TYPE_AAAA not in rdtypes | ||
| 699 | + if ipv4_denied != self._ipv4_denied or ipv6_denied != self._ipv6_denied: | ||
| 700 | + self._ipv4_denied = ipv4_denied | ||
| 701 | + self._ipv6_denied = ipv6_denied | ||
| 702 | + updated = True | ||
| 703 | + if ( | ||
| 704 | + record_key == self.key | ||
| 705 | + and not self._txt_seen | ||
| 706 | + and _TYPE_SRV in rdtypes | ||
| 707 | + and _TYPE_TXT not in rdtypes | ||
| 708 | + ): | ||
| 709 | + # Requiring the SRV bit keeps older python-zeroconf NSECs, which listed the | ||
| 710 | + # missing address types, from being misread as a TXT denial. | ||
| 711 | + self._txt_seen = True | ||
| 712 | + updated = True | ||
| 713 | + return updated | ||
| 670 | 714 | ||
| 671 | 715 | def dns_addresses( | |
| 672 | 716 | self, | |
@@ -866,6 +910,9 @@ def _load_from_cache(self, zc: Zeroconf, now: float_) -> bool: | |||
| 866 | 910 | """ | |
| 867 | 911 | cache = zc.cache | |
| 868 | 912 | original_server_key = self.server_key | |
| 913 | + # Denials are only trusted until the next cache load re-derives them. | ||
| 914 | + self._ipv4_denied = False | ||
| 915 | + self._ipv6_denied = False | ||
| 869 | 916 | cached_srv_record = cache.get_by_details(self._name, _TYPE_SRV, _CLASS_IN) | |
| 870 | 917 | if cached_srv_record: | |
| 871 | 918 | self._process_record_threadsafe(zc, cached_srv_record, now) | |
@@ -878,12 +925,16 @@ def _load_from_cache(self, zc: Zeroconf, now: float_) -> bool: | |||
| 878 | 925 | self._process_record_threadsafe(zc, cached_nsec_record, now) | |
| 879 | 926 | if original_server_key == self.server_key: | |
| 880 | 927 | # If there is a srv which changes the server_key, | |
| 881 | - # A and AAAA will already be loaded from the cache | ||
| 882 | - # and we do not want to do it twice | ||
| 928 | + # A, AAAA, and the server NSEC will already be loaded | ||
| 929 | + # from the cache and we do not want to do it twice | ||
| 883 | 930 | for record in self._get_address_records_from_cache_by_type(zc, _TYPE_A): | |
| 884 | 931 | self._process_record_threadsafe(zc, record, now) | |
| 885 | 932 | for record in self._get_address_records_from_cache_by_type(zc, _TYPE_AAAA): | |
| 886 | 933 | self._process_record_threadsafe(zc, record, now) | |
| 934 | + if self.server_key and not self._is_complete: | ||
| 935 | + cached_server_nsec_record = cache.get_by_details(self.server_key, _TYPE_NSEC, _CLASS_IN) | ||
| 936 | + if cached_server_nsec_record: | ||
| 937 | + self._process_record_threadsafe(zc, cached_server_nsec_record, now) | ||
| 887 | 938 | return self._is_complete | |
| 888 | 939 | ||
| 889 | 940 | @property | |
@@ -897,6 +948,14 @@ def _is_complete(self) -> bool: | |||
| 897 | 948 | """ | |
| 898 | 949 | return bool(self._txt_seen and (self._ipv4_addresses or self._ipv6_addresses)) | |
| 899 | 950 | ||
| 951 | + @property | ||
| 952 | + def _is_denied(self) -> bool: | ||
| 953 | + """Every address type this request needs was denied via NSEC (RFC 6762 section 6.1).""" | ||
| 954 | + query_types = self._query_record_types | ||
| 955 | + return (_TYPE_A not in query_types or (self._ipv4_denied and not self._ipv4_addresses)) and ( | ||
| 956 | + _TYPE_AAAA not in query_types or (self._ipv6_denied and not self._ipv6_addresses) | ||
| 957 | + ) | ||
| 958 | + | ||
| 900 | 959 | def request( | |
| 901 | 960 | self, | |
| 902 | 961 | zc: Zeroconf, | |
@@ -978,7 +1037,7 @@ async def async_request( | |||
| 978 | 1037 | try: | |
| 979 | 1038 | zc.async_add_listener(self, None) | |
| 980 | 1039 | while not self._is_complete: | |
| 981 | - if last <= now: | ||
| 1040 | + if last <= now or self._is_denied: | ||
| 982 | 1041 | return False | |
| 983 | 1042 | if next_ <= now: | |
| 984 | 1043 | this_question_type = question_type or (QU_QUESTION if first_request else QM_QUESTION) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments