2626from typing import Dict , Iterable , List , NamedTuple , Optional , Set , TYPE_CHECKING , Tuple , Union , cast
2727
2828from ._cache import DNSCache , _UniqueRecordsType
29- from ._dns import DNSAddress , DNSPointer , DNSQuestion , DNSRRSet , DNSRecord
29+ from ._dns import DNSAddress , DNSNsec , DNSPointer , DNSQuestion , DNSRRSet , DNSRecord
3030from ._history import QuestionHistory
3131from ._logger import log
3232from ._protocol import DNSIncoming , DNSOutgoing
33+ from ._services .info import ServiceInfo
3334from ._services .registry import ServiceRegistry
3435from ._updates import RecordUpdate , RecordUpdateListener
3536from ._utils .time import current_time_millis , millis_to_seconds
3637from .const import (
3738 _CLASS_IN ,
39+ _CLASS_UNIQUE ,
3840 _DNS_OTHER_TTL ,
3941 _DNS_PTR_MIN_TTL ,
4042 _FLAGS_AA ,
4446 _TYPE_A ,
4547 _TYPE_AAAA ,
4648 _TYPE_ANY ,
49+ _TYPE_NSEC ,
4750 _TYPE_PTR ,
4851 _TYPE_SRV ,
4952 _TYPE_TXT ,
5659_AnswerWithAdditionalsType = Dict [DNSRecord , Set [DNSRecord ]]
5760
5861_MULTICAST_DELAY_RANDOM_INTERVAL = (20 , 120 )
59- _RESPOND_IMMEDIATE_TYPES = {_TYPE_SRV , _TYPE_A , _TYPE_AAAA }
62+ _ADDRESS_RECORD_TYPES = {_TYPE_A , _TYPE_AAAA }
63+ _RESPOND_IMMEDIATE_TYPES = {_TYPE_NSEC , _TYPE_SRV , * _ADDRESS_RECORD_TYPES }
6064
6165
6266class QuestionAnswers (NamedTuple ):
@@ -78,6 +82,15 @@ def _message_is_probe(msg: DNSIncoming) -> bool:
7882 return msg .num_authorities > 0
7983
8084
85+ def construct_nsec_record (name : str , types : List [int ], now : float ) -> DNSNsec :
86+ """Construct an NSEC record for name and a list of dns types.
87+
88+ This function should only be used for SRV/A/AAAA records
89+ which have a TTL of _DNS_OTHER_TTL
90+ """
91+ return DNSNsec (name , _TYPE_NSEC , _CLASS_IN | _CLASS_UNIQUE , _DNS_OTHER_TTL , name , types , created = now )
92+
93+
8194def construct_outgoing_multicast_answers (answers : _AnswerWithAdditionalsType ) -> DNSOutgoing :
8295 """Add answers and additionals to a DNSOutgoing."""
8396 out = DNSOutgoing (_FLAGS_QR_RESPONSE | _FLAGS_AA , multicast = True )
@@ -244,12 +257,23 @@ def _add_pointer_answers(
244257 # Add recommended additional answers according to
245258 # https://tools.ietf.org/html/rfc6763#section-12.1.
246259 dns_pointer = service .dns_pointer (created = now )
247- if not known_answers .suppresses (dns_pointer ):
248- answer_set [dns_pointer ] = {
249- service .dns_service (created = now ),
250- service .dns_text (created = now ),
251- * service .dns_addresses (created = now ),
252- }
260+ if known_answers .suppresses (dns_pointer ):
261+ continue
262+ additionals : Set [DNSRecord ] = {service .dns_service (created = now ), service .dns_text (created = now )}
263+ additionals |= self ._get_address_and_nsec_records (service , now )
264+ answer_set [dns_pointer ] = additionals
265+
266+ def _get_address_and_nsec_records (self , service : ServiceInfo , now : float ) -> Set [DNSRecord ]:
267+ """Build a set of address records and NSEC records for non-present record types."""
268+ seen_types : Set [int ] = set ()
269+ records : Set [DNSRecord ] = set ()
270+ for dns_address in service .dns_addresses (created = now ):
271+ seen_types .add (dns_address .type )
272+ records .add (dns_address )
273+ missing_types : Set [int ] = _ADDRESS_RECORD_TYPES - seen_types
274+ if missing_types :
275+ records .add (construct_nsec_record (service .server , list (missing_types ), now ))
276+ return records
253277
254278 def _add_address_answers (
255279 self ,
@@ -263,13 +287,21 @@ def _add_address_answers(
263287 for service in self .registry .async_get_infos_server (name ):
264288 answers : List [DNSAddress ] = []
265289 additionals : Set [DNSRecord ] = set ()
290+ seen_types : Set [int ] = set ()
266291 for dns_address in service .dns_addresses (created = now ):
292+ seen_types .add (dns_address .type )
267293 if dns_address .type != type_ :
268294 additionals .add (dns_address )
269295 elif not known_answers .suppresses (dns_address ):
270296 answers .append (dns_address )
271- for answer in answers :
272- answer_set [answer ] = additionals
297+ missing_types : Set [int ] = _ADDRESS_RECORD_TYPES - seen_types
298+ if answers :
299+ if missing_types :
300+ additionals .add (construct_nsec_record (service .server , list (missing_types ), now ))
301+ for answer in answers :
302+ answer_set [answer ] = additionals
303+ elif type_ in missing_types :
304+ answer_set [construct_nsec_record (service .server , list (missing_types ), now )] = set ()
273305
274306 def _answer_question (
275307 self ,
@@ -299,7 +331,7 @@ def _answer_question(
299331 # https://tools.ietf.org/html/rfc6763#section-12.2.
300332 dns_service = service .dns_service (created = now )
301333 if not known_answers .suppresses (dns_service ):
302- answer_set [dns_service ] = set ( service . dns_addresses ( created = now ) )
334+ answer_set [dns_service ] = self . _get_address_and_nsec_records ( service , now )
303335 if type_ in (_TYPE_TXT , _TYPE_ANY ):
304336 dns_text = service .dns_text (created = now )
305337 if not known_answers .suppresses (dns_text ):
0 commit comments